在Spring框架中,循环依赖是指多个bean之间相互依赖,可能会导致在实例化过程中的死锁或无限递归。为了解决这个问题,Spring引入了三级缓存机制,用于存储在创建单例bean实例的过程中的相关数据。本篇博客将深入探讨Spring循环依赖和三级缓存机制,包括循环依赖的生成流程和每层缓存的作用。
1. 循环依赖的生成流程
当两个或多个bean相互依赖时,可能会导致循环依赖问题。为了更好地理解循环依赖的生成流程,我们可以通过以下步骤来说明:
- 应用程序启动,Spring容器开始初始化。
- 当创建bean A时,发现需要依赖于bean B。
- 然后,创建bean B时,又发现需要依赖于bean A。
- 此时就形成了循环依赖,Spring容器需要解决这一依赖关系。
2. 三级缓存的作用
Spring的三级缓存包括singletonObjects、earlySingletonObjects和singletonFactories。它们的作用分别如下:
- singletonObjects(一级缓存): 存储已经实例化的单例bean对象。
- earlySingletonObjects(二级缓存): 存储早期暴露的bean实例,用于解决循环依赖。
- singletonFactories(三级缓存): 存储用于创建bean实例的ObjectFactory。
大白话:
一级缓存:存储最终的单例bean,可能是原始bean对象,也可能是代理bean对象(区别在于是否需要生成代理类,比如AOP)。
二级缓存:存储提前暴露的单例bean,比如创建A的时候发现需要依赖B,但是B在一级缓存中没有,就需要创建B,创建B的时候又依赖A这时候就需要提前创建一个A放入二级缓存,提供给B使用,从而终止循环完成了B的创建。
三级缓存:用于存放bean工厂的lambda表达式,二级缓存中的代理对象就是通过lambda表达式创建的。
3. 举例说明
假设我们有两个类A和B,它们相互依赖,可以通过以下代码来说明循环依赖和三级缓存的作用:
public class A {
private B b;
public void setB(B b) {
this.b = b;
}
}
public class B {
private A a;
public void setA(A a) {
this.a = a;
}
}
在这个例子中,类A和B相互依赖,创建其中一个类的实例时,需要引用另一个类的实例。这时,Spring会使用三级缓存来解决循环依赖问题,具体流程如下:
首先创建bean主要分三步:实例化(new一个对象)、属性赋值(对象赋值),初始化(最终bean对象)
- 实例化A对象,并放入createSet中,并生成lambda表达式放入三级缓存。
- 给A对象赋值时发现依赖于B,但是从一级缓存和二级缓存都没有找到bean。于是会创建一个B对象,刚开始是实例化B对象,然后接着给B对象赋值,此时发现B依赖于A。但是从一二级缓存都没办法找到A对象bean,同时发现createSet中有A对象(于是知道A对象在创建中),便根据三级缓存中的lambda表达式创建一个提前暴露的A对象bean放到二级缓存(创建的是否为代理对象也是这一步完成),同时把三级缓存中A的lambda表达式清除。然后B对象就可以完成属性赋值。从而完成后面的初始化操作把B对象放入一级缓存。
- 上面完成了B的初始化,就可以完成A对象的属性赋值,同时把A在二级缓存的提前暴露bean移到一级缓存。
4. 其他
4.1 三级缓存无法解决的循环依赖场景
如下通过构造函数创建注入的bean无法通过三级缓存解决,原因是这创建bean的第一步实例化时就会调用构造方法。因为默认调用的是无参构造函数,但是由于这里声明了有参构造函数所以调用的时候发现BService 为null,就无法完成A的实例化,因此在解决循环依赖时就无法生成A的提前暴露bean,因此无法解决循环依赖。
@Service
public class AServiceImpl implements AService{
private BService bService;
public AServiceImpl(BService bService) {
this.bService = bService;
}
@Override
public void a() {
System.out.println("hello 方法A");
bService.b();
}
}
解决方法:
- 改为非构造函数注入bean。
- 加上 @Lazy注解
@Service
public class AServiceImpl implements AService{
private BService bService;
@Lazy
public AServiceImpl(BService bService) {
this.bService = bService;
}
@Override
public void a() {
System.out.println("hello 方法A");
bService.b();
}
}
为什么@Lazy 注解可以解决循环依赖?因为@Lazy注解会生成一个BService 代理对象,然后A就可以顺利完成实例化,从而解决循环依赖问题。