Spring IoC、循环依赖

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中:
img

依赖注入:
  当完成初始化IoC容器后,如果bean没有设置lazy-init属性,那么bean的实例就会在初始化IoC完成之后,及时地进行初始化。初始化时会先建立实例,然后根据配置利用反射对实例进行进一步操作:
img

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之间构成了一个环形。
img
当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异常。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值