赞
踩
Spring循环依赖指的是两个或多个Bean之间相互依赖,形成一个环状依赖的情况。通俗的说,就是A依赖B,B依赖C,C依赖A,这样就形成了一个循环依赖的环。
Spring循环依赖通常会导致Bean无法正确地被实例化,从而导致应用程序无法正常启动或者出现异常。因此,Spring循环依赖是一种需要尽量避免的情况。
在使用构造函数注入Bean时,如果两个Bean之间相互依赖,就可能会形成构造函数循环依赖,例如:
@Component
public class A {
private B b;
public A(B b) {
this.b = b;
}}
==============
@Component
public class B {
private A a;
public B(A a) {
this.a = a;
}}
上述代码,A、B的构造函数分别需要创建对方,A依赖B,B依赖A,它们之间形成了一个循环依赖。
当Spring容器启动时,它会尝试先实例化A,但是在实例化A的时候需要先实例化B,而实例化B的时候需要先实例化A,这样就形成了一个循环依赖的死循环,从而导致应用程序无法正常启动。
在使用属性注入Bean时,如果两个Bean之间相互依赖,就可能会形成属性循环依赖。例如:
@Component
public class A {
@Autowired
private B b;
}
=============
@Component
public class B {
@Autowired
private A a;
}
类似的,同样Spring在实例化A时会注入B,而注入B时又需要注入A,形成循环依赖
要了解如何解决Spring的循环依赖,就必须要从Spring对Bean的创建过程入手,从原理上去理解,而不是单纯记忆
(1) 首先Spring容器启动之后,会根据使用不同类型的Application Context,通过不同的方式去加载Bean配置,如xml方式、注解方式,将这些Bean配置加载到容器中,作为Bean定义包装成BeanDefinition对象保存起来,为下一步创建Bean做准备。
(2) 根据加载的Bean定义信息,通过反射来创建Bean实例,如果是普通Bean,则直接创建Bean,如果是FactoryBean,说明真正要创建的对象为getObject()的返回值,调用getObject()将返回值作为Bean。
(3) 目前已经完成了Bean实例的创建,还需要对依赖的属性进行装配,例如,平时开发中Controller中,往往需要将Service Bean注入进来,循环依赖也是在这一步解决的,后面会详细说明,如果通过@Autowired注解注入的成员变量,则会通过AutowiredAnnotationBeanPostProcessor后置处理器进行注入,如果xml自动注入,则会根据按名字自动装配和按类型自动装配分别进行处理。
(4) 自动装配完成后,将完整的Bean对象保存到Spring 缓存中,接下来进入Bean的初始化流程,执行Bean的后置处理器的前置处理方法,如果Bean本身实现了InitializingBean接口,就去执行对应的afterPropertiesSet()方法,最后再执行Bean的后置处理器的后置处理方法。
回到上述的依赖循环案例,A、B相互依赖时,产生的循环依赖现象还需进一步细分:
注意:只有单例的 Bean 存在循环依赖的情况,Spring才可以解决,原型(Prototype)情况下,Spring 会直接抛出异常。
Spring 不支持基于构造器注入的循环依赖。 但是假如 AB 循环依赖,如果一个是构造器注入,一个是 setter 注入呢?
看看几种情形:
第四种可以而第五种不可以的原因是 Spring 在创建 Bean 时默认会根据自然排序进行创建,所以 A 会先于 B 进行创建。
简单总结,当循环依赖的实例都采用 setter 方法注入的时候,Spring 可以支持,都采用构造器注入的时候,不支持,构造器注入和 setter 注入同时存在的时候,看天。
bean的创建流程:
依赖注入就发生在第二步,属性赋值,结合这个过程,Spring 通过三级缓存解决了循环依赖:
Map<String,Object>
singletonObjects,单例池,用于保存实例化、属性赋值(注入)、初始化完成的 bean 实例Map<String,Object>
earlySingletonObjects,早期曝光对象,用于保存实例化完成的 bean 实例Map<String,ObjectFactory<?>>
singletonFactories,早期曝光对象工厂,用于保存 bean 创建工厂,以便于后面扩展有机会创建代理对象。我们来看一下三级缓存解决循环依赖的过程:
当 A、B 两个类发生循环依赖时:
A 实例的初始化过程:
1
2
5
所以,我们就知道为什么 Spring 能解决 setter 注入的循环依赖了,因为实例化和属性赋值是分开的,所以里面有操作的空间。如果都是构造器注入的化,那么都得在实例化这一步完成注入,所以自然是无法支持了。
所以,我们就知道为什么 Spring 能解决 setter 注入的循环依赖了,因为实例化和属性赋值是分开的,所以里面有操作的空间。如果都是构造器注入的化,那么都得在实例化这一步完成注入,所以自然是无法支持了。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。