1.什么是IoC和DI?
IOC(控制反转):
没有引入IoC容器之前,对象A依赖对象B,那么A对象在实例化或者运行到某一点的时候,自己必须主动创建对象B或者使用已经创建好的对象B,其中不管是创建还是使用已创建的对象B,控制权都在自己手上。
引入了Ioc容器之后,对象A和对象B之间失去了直接联系,所以,当对象A实例化和运行时,如果需要对象B的话,IoC容器会主动创建一个对象B注入到对象A所需要的地方。对象A获得依赖对象B的过程,由主动行为变成了被动行为,即把创建对象交给了IoC容器处理。
DI(依赖注入):
IoC容器在运行期间,动态地将某种依赖关系注入到对象之中。
2.Ioc的优缺点
优点:
1、IoC生成对象的方式转为外置方式,也就是把对象生成放在配置文件里进行定义,这样,当我们更换一个实现子类将会变得很简单,只要修改配置文件就可以了,完全具有热插拨的特性。
2、可维护性比较好,非常便于进行单元测试,便于调试程序和诊断故障。
缺点:
1、由于引入了第三方IOC容器,生成对象的步骤变得复杂。
2、由于IOC容器生成对象是通过反射方式,在运行效率上有一定的损耗。
3.IoC原理
IOC容器要生成的对象是通过配置文件中给出定义的,用Java反射技术,根据配置文件中给出的类名生成相应的对象。
在Spring IoC中经常用到一个设计模式,即工厂模式。工厂模式是指当应用程序中A组件需要B组件协助时,并不是在A组件中直接实例化B组件对象,而是通过B组件的工厂获取,即该工厂可以生成某一类型组件的实例对象。在这种模式下,A组件无需与B组件以硬编码的方式耦合在一起,而只需与B组件的工厂耦合。
IoC初始化:
初始化的过程主要就是读取XML资源,并解析,最终注册到Bean Factory中:
依赖注入:
当完成初始化IoC容器后,如果bean没有设置lazy-init属性,那么bean的实例就会在初始化IoC完成之后,及时地进行初始化。初始化时会先建立实例,然后根据配置利用反射对实例进行进一步操作:
4.Spring中Bean的作用域(Singleton、prototype)
这里只介绍其中两种:singleton和prototype
singleton scope(单例作用域):
spring的bean默认就是单例的。spring的单例不同于单例设计模式,单例设计模式里的单例类,每一个classLoader只能产生一个实例,但是spring的单例作用域,每一个类,在一个容器内只能产生一个实例,但是多容器的情况下还是可以产生多个实例。
单例bean有延迟加载和立即加载两种加载方式,其中立即加载模式会在容器启动的时候就创建bean,而延迟加载会在容器启动后,使用到bean的时候再加载它。
prototype scope(原型作用域):
该bean每次被注入,或者使用getBean()方法获取时,都返回一个新的实例。
spring不会管理作用域为prototype的bean的生命周期,生成实例之后就不再记录它们。因此,虽然所有作用域的bean的initialization生命周期回调方法会被执行,但是prototype作用域的bean的destruction回调并不会被执行,客户代码必须自己清理占用的资源。对于这种情况,spring提供了自定义的bean post processor,来处理prototype-scoped bean的清理。
5.依赖注入的方式
通过构造器注入、通过属性注入
通过构造器注入:
class B {
private A a;
public B(A a) {
this.a = a;
}
public A getA() {
return a;
}
}
通过属性注入
class A {
private B b;
public B getB() {
return b;
}
public void setB(B b) {
this.b = b;
}
}
6.Spring引用循环依赖
如果多个bean存在循环依赖,在Spring容器启动后,只有当获取的第一个bean是通过属性注入依赖的singleton时,才会成功,别的情况都会失败。
循环引用的bean之间必然会构成一个环,如下图所示,A、B、C之间构成了一个环形。
当Spring容器在创建A时,会发现其引用了B,从而会先去创建B。同样的,创建B时,会先去创建C,而创建C时,又先去创建A。最后A、B、C之间互相等待,谁都没法创建成功。
要想打破这个环,这个环中至少需要有一个bean可以在自身的依赖还没有得到满足前,就被创建出来(最起码要被实例化出来,可以先不注入其需要的依赖)。这种bean只能是通过属性注入依赖的类,因为它们可以先使用默认构造器创建出实例,然后再通过setter方法注入依赖。而通过构造器注入依赖的类,在它的依赖没有被满足前,无法被实例化。而且这个bean,还必须是singleton,不能是prototype。
例如:
<bean id="singletonA" class="ccc.spring.circulardependencies.field.A" lazy-init="true">
<property name="b" ref="singletonB"/>
</bean>
<bean id="singletonB" class="ccc.spring.circulardependencies.field.B" lazy-init="true">
<property name="a" ref="singletonA"/>
</bean>
Spring容器启动后,如果去获取singletonA,那么容器的操作步骤大致如下:
1.尝试创建bean singletonA,发现singletonA是singleton,且不是通过构造器注入依赖,那么先使用默认构造器创建一个A的实例,并保存对它的引用,并且将singletonA标记为“正在创建中的singleton”。然后发现singletonA依赖了singletonB,所以尝试创建singletonB。
2.尝试创建bean singletonB,发现singletonB是singleton,且不是通过构造器注入依赖,那么先使用默认构造器创建一个B的实例,并保存对它的引用,并且将singletonB标记为“正在创建中的singleton”。然后发现singletonB依赖了singletonA,所以尝试创建singletonA。
3.尝试创建singletonA,注意,这时Spring容器发现singletonA“正在创建中”,那么就不会再去创建singletonA,而是返回容器之前保存了的对singletonA的引用。
4.容器将singletonA通过setter方法注入到singletonB,从而singletonB完成创建。
5.容器将singletonB通过setter方法注入到singletonA,从而singletonA完成创建。
在第1步中,容器会保存对singletonA的引用,在第3步中,再返回对singletonA的引用,从而可以成功创建那些依赖了singletonA的bean(singletonB)。这样,循环依赖的环就在singletonA这个点这里被打破。
为什么prototype不能成为打破这个环的一个点呢?
Spring容器只会对singleton保存引用,而对于prototype,并不会对其保存引用。(如果也对prototype保存引用,那么其实它就变成了singleton。)
在循环依赖的环里,只要有一个bean,是通过属性注入依赖,并且是singleton,那么这个环就可以被打破,无论获取他们的顺序是怎样。
因为当Spring容器遍历那些循环依赖的bean时,只要遍历到那种已经遍历过一次的bean,并且它们不是通过属性注入依赖的singleton时,就会直接抛出BeanCurrentlyInCreationException异常。