Spring循环依赖包括:
1.构造器循环依赖,
//构造器循环依赖A,B方法互相调用
@Service
public class A {
public A(B b) { }
}
@Service
public class B {
public B(A a) {
}
}
//结果:项目启动失败,发现了一个cycle,AB直接调用死循环,不能结束
2.field属性注入循环依赖
//注入循环依赖
@Service
public class A1 {
@Autowired
private B1 b1;
}
@Service
public class B1 {
@Autowired
public A1 a1;
}
//结果:项目启动成功
3.field属性注入循环依赖(prototype)
//field属性注入循环依赖(prototype)
@Service
@Scope("prototype")
public class A1 {
@Autowired
private B1 b1;
}
@Service
@Scope("prototype")
public class B1 {
@Autowired
public A1 a1;
}
//结果:项目启动失败,发现了一个cycle
结论
构造器注入和prototype类型的field注入发生循环依赖时都无法出书画
field注入单例的bean时,尽管有循环依赖,但bean仍然可以被成功初始化
Spring创建bean的过程
Spring 创建好BeanDefinition之后,会开始实例化bean。并且对bean的依赖项进行填充
Spring实例化bena是通过ApplicationContetxt.getBean()方法进行的,实例化底层用了CGLIB或java反射技术
要理解两点
Spring是通过递归的方式获取目标bean及其所依赖的bean的
2.Spring实例化一个bean的时候,是分两部进行的,首先实例化目标bean,然后为其注入属性
也就是说spring再创建一个实例化bean的时候,首先递归的实例化所依赖的所有bean,直到某个bean没有其它依赖的bean,此时就会将该实例返回,然后反递归的将获取到的bean设置为各个上层bean的属性
比如说spring在实例化A对象是,会先实例化A的依赖项B,C。此时发现B又依赖D,那么就会先实例化Bean对象D,再实例化B,C再实例化A。
实例化过程中有一个重要的类DefaultListableBeanFactory(默认列表Bean工厂)
他又三个map类型的属性,是解决问题的关键,是IOC的三级缓存的表现,对于单例来说,在Spring在Spring容器整个生命周期内,有且只有一个对象,所以很容易想到,这个对象应该存在Cache中,Spring 为了解决单例的循环依赖问题,使用了三级缓存,就是DefaultListableBeanFactory(默认列表Bean工厂)类的三个Map。
三级缓存(单例工厂)singletonFactories存放ObjectFactory传入的时匿名内部类ObjectFactory.getObject() 方法最终会调用getEarlyBeanReference()进行处理,返回创建bean实例化的lambda表达式。
二级缓存(早期的单例对象)earlySingletonObjects存放bean,保存半成品bean实例,当对象需要被AOP代理是,保存bean实例的bean代理
一级缓存(单利池)singletonObjects存放完整的bean实例
/**单例对象的缓存:从bean名称到bean实例。 */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/**早期单例对象的缓存:从bean名称到bean实例。 */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
/**单例工厂的缓存:对象工厂的bean名称。 */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
单例注入bean如何解决循环依赖?
假设循环注入是A-B-A:A依赖了B,B又依赖了A;
@Component
public class A {
private B b;
public void setB(B b) {
this.b = b;
}
}
@Component
public class B {
private A a;
public void setA(A a) {
this.a = a;
}
}
Spring创建bean的流程中的三级缓存解决了依赖循环的问题
- Spring通过Application.getBean()获取A对象的实例,由于Spring容器缓存中还没有A对象实例,因此会创建A对象,
- A对象初始化之后,Spring 将这个还没有设置属性的A放入缓存中
- 然后发现了其依赖B对象,因而会先创建B对象的实例
- 此时A和B都已经被创建了,只不过A和B的对象属性都还没有设置进去
- Spring创建B对象之后,优化发现B依赖了A,因此会再尝试调用A,通过Application.getBean()获取A对象的实例,但此时Spring中已经存在了一个A对象,会将这个对象返回
- 此时A对象的属性b和B对象的属性A都已经被设置目标对象的实例了
注意:前面在为对象B设置属性a的时候,这个A类型属性还是个半成品。但是这个A是一个引用,其本质上还是最开始就实例化的A对象,其引用地址是同一个。
使用二级缓存能不能解决循环依赖?
答案是不可以:
缺少singletonObjects时,将无法产生对象
缺少singletonFactories时,无法产生代理对象
缺少earlySingletonObjects时,无法保证始终只有一个代理对象
那么为什么prototype类型的和构造器类型的Spring无法解决循环依赖呢?
因为A中注入了B,那么A在关键的方法addSingletonFactory()把A放入singletonFactories之前就去初始化B,导致三级缓存根本没有A,所以会发生死循环,Spirng发现之后就抛出异常了。
Spring是如何发现异常的呢,本质上是根据一Bean状态给Bean进行mark如果递归调用时发现Bean正在创建中,那么抛出循环依赖的异常即可