spring是一个为了简化企业级开发,它是轻量级的、使用IoC、AOP等进行开发的一站式框架。比如,控制反转&依赖注入、面向切面编程、spring事务管理、通过spring继承其他框架(Spring继承jdbc、mybatis等)。
依赖关系:当一个对象a的某些操作需要通过调用另一个对象b中的方法来实现时,说明a依赖于对象b,a与b是依赖关系。
IoC:控制反转
使用者之前使用对象的时候都需要自己手动的创建和组装,而现在这些创建和组装都交给了spring容器来进行管理,需要使用这个对象的时候,我们可以直接去spring容器中查找使用这个对象。之前创建和组装对象都是我们自己手动的进行创建,现在交由spring来进行创建和组装了,对象的构建被反转了,就叫做控制反转。IOC是面向对象编程的一种设计原则,它可以降低系统代码的耦合度,使系统更加有利于维护和扩展。
依赖注入是Spring容器中创建对象时给其设置依赖对象的方式,比如给Spring一个清单,清单中列出了需要创建B对象以及其他的一些对象(可能包含B类型中需要依赖的对象),此时spring 在创建B的时候,会看B对象需要依赖哪些对象,然后去spring容器中查找看没有这些对象,如果有就去将其创建好,然后将其传递给B对象;可能B需要依赖于很多对象,B创建之前完全不需要知道其他对象是否存在或者其他对象在哪里以及被他们是如何创建,而spring容器会将B依赖对象主动创建好并将 其注入到B中去,比如spring容器创建B的时候,发现B需要依赖于A,那么spring容器在清单中找到A的定义并将其创建好之后,注入到B对象中。
IOC容器
IOC容器是具有依赖注入功能的容器,负责对象的实例化、对象的初始化,对象和对象之间依赖关系配置,对象的销毁、对外提供对象的查找等操作,对象的整个生命周期都是由容器来控制。我们不需要我们手动的创建对象,需要使用的对象都保存在IOC容器进行管理,当我们需要使用的时候直接从IOC容器中直接获取即可。
Bean概念
由Spring容器管理的对象称为Bean对象。
单例bean(scope = singleton)
当scope的值设置为singleton的时候,整个spring容器中只会存在一个bean实例,通过容器多次查找 bean的时候(调用BeanFactory的getBean方法或者bean之间注入依赖的bean对象的时候),返回的 都是同一个bean对象,singleton是scope的默认值,所以spring容器中默认创建的bean对象是单例的, 通常spring容器在启动的时候,会将scope为singleton的bean创建好放在容器中(有个特殊的情况,当 bean的lazy被设置为true的时候,表示懒加载,那么使用的时候才会创建),用的时候直接返回。
使用注意:
单例bean是整个应用共享的,所以需要考虑到线程安全问题,之前在Spring MVC的时候,spring MVC中controller默认是单例的,有些开发者在controller中创建了一些变量,那么 这些变量实际上是共享的,controllet可能被多个线程同时访问,这些线程并发去修改controller中的共享变量,可能会出现数据错乱的问题,所以使用的时候需要特别注意。
总结:
什么是注解?
注解:可以标记包,类,方法,属性,参数等。和 Javadoc 不同, Java 标注可以通过反射获取标注内容。注解是对代码的一种增强,可以在代码编译或者程序运行期间获取注解的信息,然后根据这 些信息做各种牛逼的事情。
@ComponentScan
用于去扫描某些包及其子包中所有的类,然后将满足一定条件的类作为bean注册到 spring容器容器中。
@ComponentScan工作的过程:
- @Target(ElementType.TYPE)
- @Retention(RetentionPolicy.RUNTIME)
- @Documented
- @Indexed
- public @interface Component {
- String value() default "";
- }
从定义中可以看出,这个注解可以用在任何类型上面。
通常情况下将这个注解用在类上面,标注这个类为一个组件,默认情况下,被扫描的时候会被作为bean注册到容器中。value参数,用来指定被注册为bean时的,用来指定bean的名称。(不指定,默认为类名首字母小写)
- @Target({ElementType.TYPE})
- @Retention(RetentionPolicy.RUNTIME)
- @Documented
- @Component
- public @interface Repository {
- @AliasFor(annotation = Component.class)
- String value() default "";
- }
- /*
- Repository上面有@Component注解。
- value参数上面有 @AliasFor(annotation = Component.class) ,设置value参数的时候,也相
- 当于给 @Component 注解中的value设置值。
- */
其他两个注解 @Service、@Controller 源码和 @Repository 源码类似。
这4个注解本质上是没有任何差别,都可以用在类上面,表示这个类被spring容器扫描的时候,可以作为 一个bean组件注册到spring容器中。@controller通常用来标注controller层组件,@service注解标注service层的组件,@Repository标注 dao层的组件,这样可以让整个系统的结构更清晰,当看到这些注解的时候,会和清晰的知道属于哪个层,对于spring来说,将这3个注解替换成@Component注解,对系统没有任何影响,产生的效果是一样的。
@Import
按模块的方式进行导入,需要哪个导入哪个,不需要的时候,直接修改一下总的配置类,调整一下 @Import就可以了,非常方便。@Import可以用来批量导入任何普通的组件、配置类,将这些类中定义的所有bean注册到容器。
@Conditional:注解可以标注在spring需要处理的对象上(配置类、@Bean方法),相当于加了个条件判断,通过判断的结果,让spring觉得是否要继续处理被这个注解标注的对象。
@Autowired、@Resource、 @Primary、@Qulifier
@Autowired:注入依赖对象
作用:实现依赖注入,spring容器会对bean中所有字段、方法进行遍历,标注有@Autowied注解的,都会进行注入。
- @Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD, ElementType.ANNOTATION_TYPE})
- @Retention(RetentionPolicy.RUNTIME)
- @Documented
- public @interface Autowired {
- boolean required() default true;
- }
@Resource:注入依赖对象
作用:实现依赖注入,spring容器会对bean中所有字段、方法进行遍历,标注有@Resourse注解的,都会进行注入。
- @Documented
- @Retention(RUNTIME)
- @Target(TYPE)
- public @interface Resources {
- /**
- * Array used for multiple resource declarations.
- */
- Resource[] value();
- }
@Qualifier:限定符
作用:可以在依赖注入查找候选者的过程中对后选择进行过滤;可以用在类上;也可以用在依赖注入的地方,可以对候选者的查找进行过滤
@Autowired和@Resource的区别?
@Scope、@DependsOn、@ImportResource、@Lazy
假设系统中有两个模块:module1和module2,两个模块是独立开发的,module2会用到module1中的一些类,module1会将自己打包为jar,提供给module2使用。
在mondule1中有三个类:Service1、Service2(Service2中需要用到Service1)、Module1Confi(spring配置类)
在mondule2中有三个类:Service1、Service2(使用module2中的Service1,使用module1中的Service2)、Module1Config(spring配置类)
测试运行报错,原因在于因为两个模块中都有Service1,被注册到 spring容器的时候,bean名称会冲突,导致注册失败。
如何解决?
对module1中的Service1进行修改?
这个估计是行不通的,module1是别人以jar的方式提供给我们的, 源码我们是无法修改的。 而module2是我们自己的开发的,里面的东西我们可以随意调整,那么我们可以去修改一下module2中 的Service1,可以修改一下类名,或者修改一下这个bean的名称,此时是可以解决问题的。 不过大家有没有想过一个问题:如果我们的模块中有很多类都出现了这种问题,此时我们一个个去重构,还是比较痛苦的,并且代码重构之后,还涉及到重新测试的问题,工作量也是蛮大的,这些都是风险。 而spring中的父子容器就可以很好的解决上面这种问题。
什么是父子容器?
创建spring容器的时候,可以给当前容器指定一个父容器。
BeanFactory的方式
- //创建父容器
- parentFactory DefaultListableBeanFactory parentFactory = new DefaultListableBeanFactory(); //创建一个子容器
- childFactory DefaultListableBeanFactory childFactory = new DefaultListableBeanFactory();
- //调用setParentBeanFactory指定父容器
- childFactory.setParentBeanFactory(parentFactory);
ApplicationContext的方式
- //创建父容器
- AnnotationConfigApplicationContext parentContext = new AnnotationConfigApplicationContext();
- //启动父容器
- parentContext.refresh();
- //创建子容器
- AnnotationConfigApplicationContext childContext = new AnnotationConfigApplicationContext(); //给子容器设置父容器
- childContext.setParent(parentContext);
- //启动子容器
- childContext.refresh();
特点:
Springmvc中只使用一个容器是否可以?
只使用一个容器是可以正常运行的
那么springmvc中为什么需要用到父子容器?
我们通常使用Spring mvc 的时候,采用3层结构,controller、service、dao;父容器中会包含dao层和service层,而子容器中包含的只有Controller,这两个容器组成了父子容器的结构,controller层通常注册service层的bean。
采用父子容器可以避免有些人在service1层去注入controller层的bean,导致整个依赖层次是比较混乱的。
父容器和子容器的需求也是不一样的,比如父容器中需要有事务的支持,会注入一些支持事务的组件,而子容器中controller完全用不到这些,对这些并不关心,子容器中需要注入一下springmvc相关的bean,而这些bean父容器中同样是不会用到的,也是不关心一些东西,将这些相互不关心的东西隔开,可以有效的避免一些不必要的错误,而父子容器加载的速度也会快一些。
spring中国际化是通过MessageSource这个接口来支持的 ,spring国际化这块有个实现类,可以检测到配置文件的变化,就可以解决你这个问题。
什么是循环依赖?
多个bean之前相互依赖,形成了一个闭环
A依赖B、B依赖C、C依赖于A
- public class A{
- B b;
- }
- public class B{
- C c;
- }
- public class C{
- A a;
- }
如何检测是否存在循环依赖?
使用一个列表来记录正在创建中的bean,bean创建之前,先去记录中看一下自己是否存在列表中,存在,说明存在循环依赖,如否,就加入到这个列表中,bean创建完成后,将其从列表中移出。
Spring如何解决循环依赖的问题?
使用spring内部的三级缓存来解决循环依赖问题。它只针对属性注入,而构造方法注入,无法解决循环依赖。
三级缓存,也可以这样理解:把每一级缓存当做3个存储不同对象的Map。
A,B 循环依赖,先初始化 A,先暴露一个半成品 A,再去初始化依赖的 B,初始化 B 时如果发现 B 依赖 A,也就是循环依赖,就注入半成品 A,之后初始化完毕 B,再回到 A 的初始化过程时就解决了循环依赖,在这里只需要一个 Map 能缓存半成品 A 就行了,也就是二级缓存就够了,但是这个二级缓存存的是 Bean 对象,如果这个对象存在代理,那应该注入的是代理,而不是 Bean,此时二级缓存无法及缓存 Bean,又缓存代理,因此三级缓存做到了缓存工厂 ,也就是生成代理,这总结起来:二级缓存就能解决缓存依赖,三级缓存解决的是代理。

将业务之外的代码,例如事务提交,获得sqlsession,获得接口代理,事务提交,关闭sqlsession等管理起来。使用预编译的方式和动态代理的方式,使用程序可以统一维护的一种技术。
原理:使用jdk代理和动态代理来创建代理对象,通过代理对象来访问目标对象,而代理对象融入增强的代码,最终起到对目标对象增强的效果。
作用:可以将业务逻辑之间进行隔离,降低耦合度,使得开发维护方便。
AOP到底为程序带来了哪些好处?
可以将一些公共的,重复出现的非业务代码进行提取,然后使用动态代理的方式,为我们的业务代码横切添加额外提取的功能,而且不需要显示的在业务代码中调用,可以通过一个代理对象,来帮助我们调用这些抽取出来的方法。
目标对象(target):目标对象指将要被增强的对象,即包含主业务逻辑的类对象
连接点(JoinPoint):连接点,程序执行的某一个点,比如执行某个方法,在spring AOP中Join Point总是表示一个方法的执行。
代理对象(Proxy):AOP会通过代理的凡是,对目标对象生成一个代理对象,代理对象中会加入需要增强功能,通过代理对象来间接的方式目标对象,起到增强目标对象的效果。
通知(Advice):需要在目标对象中增强的功能,如上面说的:业务方法前验证用户的功能、方法执行之后打印方法的执行日志。
通知中有连个重要的信息:方法的什么地方,执行什么操作,这两个信息通过通知来指定。
方法的什么地方?之前、之后、包裹目标方法、方法抛出异常后等。
如:
切入点(Pointcut):用来指定需要使用到哪些地方,比如需要用在哪些类的方法上,切入点就是做这个配置的
切面(Aspect):通知和切入点的组合。切面来定义在哪些地方执行什么操作
顾问(Advisor):Advisor其实就是Pointcut和Advice的组合,Advice是要增强的逻辑,而增强的逻辑要在什么地方执行时通过Pointcut来指定的,所以Advice必须与Pointcut组合在一起,就诞生了Advisor这个类,spring AOP中提供了一个Advisor接口将Pointcut与Advice的组合起来。
@Aspect有5种通知
- @Aspect
- public class BeforeAspect {
-
- @Before("execution(* com.Service1.*(..))")
- public void before(JoinPoint joinPoint) {
- System.out.println("我是前置通知!");
- }
- }
- /*
- 1. 类上需要使用@Aspect
- 2. 任意方法上使用@Before标注,将这个方法作为前置通知,目标方法被调用之前,会自动回调
- 这个方法
- 3. 被@Before标注的方法参数可以为空,或者为JoinPoint类型,当为JointPoint类型时,必须
- 为第一个参数
- 4. 被@Before标注的方法名称可以随意命名,符合java规范就行,其他通知也类型
- */
-
环绕通知会包裹目标方法的执行,可以在通知内部调用ProceedingJoinPoint.process方法继续执行下一个拦截器
用起来和@Before类似,但是有两点不一样
1. 若需要获取目标方法的信息,需要将ProceedingJoinPoint作为第一个参数
2. 通常使用Object类型作为方法的返回值,返回值也可以为void
特点:环绕通知比较特殊,其他4种类型的通知都可以用环绕通知来实现。
@After:后置通知
@AfterReturning:返回通知
返回通知,在方法返回结果之后执行
特点:
@AfterThrowing:异常通知
在方法抛出异常之后会回调@AfterThrowing标注的方法。
@AfterThrowing标注的方法可以指定异常的类型,当被调用的方法触发该异常及其子类型的异常之后,会触发异常方法的回调,也可以不指定异常类型,此时会匹配所有异常。
特点: 不论异常是否被异常通知捕获,异常还会继续向外抛出。
| 通知类型 | 执行时间点 | 可获取返回值 | 目标方法异常时是否会执行 |
| @Before | 方法执行之前 | 否 | 是 |
|
@Around
| 环绕方法执行 | 是 | 自己控制 |
| @After | 方法执行之后 | 否 | 是 |
| @AfterReturning | 方法执行之后 | 是 | 否 |
| @AfterThrowing | 方法发生异常之后 | 否 | 是 |

@EnableAspectJAutoProxy:用来启用自动代理的创建,简单点理解:会找到容器中所有标注有@Aspect注解的bean以及Advisor类型的bean,会将他们转换为Advisor 集合,spring会通过Advisor集合对容器中满足切入点表达式的bean生成代理对象,整个都是 spring容器启动的过程中自动完成的。