• 聊聊Spring 的循环依赖问题
  • 发布于 1天前
  • 13 热度
    0 评论
前言
spring的循环依赖问题,大家应该司空见惯了吧~ 这是一个非常经典的问题,本文聊聊~~

1. 什么是循环依赖?
循环依赖指两个或多个Bean之间互相依赖形成闭环。比如:
@Service
public class BeanA {
    @Autowired
    private BeanB beanB;
}
// 堆代码 duidaima.com
@Service
public class BeanB {
    @Autowired
    private BeanA beanA;
}
像这种,A依赖B,而B又反过来依赖于A,就是典型的循环依赖。

2.循环依赖就一定是问题嘛?
循环依赖一定就有问题嘛?
其实不一定的。比如这个例子:
//类A依赖了属性b
class A{
 public B b;
}

//类B依赖了属性a
class B{
 public A a;
}
这个也算是循环依赖,但是它却不是问题~ 我们可以正常创建的:
A a = new A();
B b = new B();

a.b = b;
b.a = a;
3. spring 的循环依赖一定有问题嘛?
我们聊得比较多的,都是spring的循环依赖。那么,spring的循环依赖一定会有问题嘛?其实不是的,比如回答这个例子:
@Service
public class BeanA {
    @Autowired
    private BeanB beanB;
}

@Service
public class BeanB {
    @Autowired
    private BeanA beanA;
}
spring容器启动运行,并不会报错!那就是不存在spring的循环依赖问题嘛?不是的,这个例子,之所有不报错,是因为Spring 默认情况下,(单例 + setter 注入)的循环依赖问题,已经解决了。 但是呢,构造器注入循环依赖,还是有问题的。
我们看这个例子,构造器注入:
@Service
public class ServiceA {
    private final ServiceB serviceB;
    // 构造器注入 ServiceB
    public ServiceA(ServiceB serviceB) {
        this.serviceB = serviceB;
    }
}

@Service
public class ServiceB {
    private final ServiceA serviceA;

    // 构造器注入 ServiceA
    public ServiceB(ServiceA serviceA) {
        this.serviceA = serviceA;
    }
}
启动后,马上的就报错了:

Description:

The dependencies of some of the beans in the application context form a cycle:

┌─────┐
|  serviceA defined in file [SpringCircularDependency\target\classes\com\example\springcirculardependency\service\ServiceA.class]
↑     ↓
|  serviceB defined in file SpringCircularDependency\target\classes\com\example\springcirculardependency\service\ServiceB.class]
└─────┘

为什么构造器注入循环依赖无法解决?其实, 字段注入 /setter 注入的方式,通过提前暴露到三级缓存,来解决循环依赖问题啦~字段注入 /setter 注入的流程是:实例化 Bean→提前暴露到三级缓存→注入依赖(依赖可通过缓存获取)。而构造器注入的流程是:需要依赖才能实例化 Bean,此时三级缓存中还没有任何关于该 Bean 的信息,无法提前暴露,因此它的循环依赖问题还存在~~

4. setter 注入方式,spring是如何解决循环依赖问题的呢?
我们提到,setter 注入,通过暴露三级缓存来解决spring三级缓存的问题,那么,它是如何解决的呢?
4.1 三级缓存作用
Spring 在DefaultSingletonBeanRegistry中定义了三个关键缓存(本质是 HashMap),它们是处理循环依赖的核心:
// 一级缓存:存储完全初始化完成的单例Bean
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 二级缓存:存储提前暴露的“半成品”Bean(已实例化但未初始化)
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
// 三级缓存:存储Bean的实例工厂,用于生成半成品Bean
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
三级缓存的作用
singletonObjects:最终可用的 “成品” Bean,已完成属性注入和初始化
earlySingletonObjects:临时存储的 “半成品” Bean,仅完成实例化(new 出对象),未完成属性注入和初始化
singletonFactories:存储生成半成品 Bean 的工厂,用于在需要时提前暴露 Bean
4.2 循环依赖处理流程
假设,以 A 依赖 B、B 依赖 A 为例,看看 Spring 如何通过三级缓存解决循环依赖~
1.A 的创建流程
步骤 1:Spring 尝试创建 A,先检查一级缓存(无),标记 A 为 “正在创建”
步骤 2:实例化 A(执行new ServiceA()),此时 A 是半成品(无属性值)
步骤 3:将 A 的工厂放入三级缓存:
DefaultSingletonBeanRegistry.addSingletonFactory
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    synchronized (this.singletonObjects) {
        if (!this.singletonObjects.containsKey(beanName)) {
            this.singletonFactories.put(beanName, singletonFactory); // 放入三级缓存
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }
}
步骤 4:给 A 注入属性时发现依赖 B,暂停 A 的创建,转去创建 B
2. B 的创建与循环依赖解决
步骤 1:创建 B,同样实例化后放入三级缓存,准备注入属性时发现依赖 A
步骤 2:查询 A 的缓存:一级缓存无→二级缓存无→从三级缓存获取 A 的工厂,通过工厂生成 A 的半成品,放入二级缓存,删除三级缓存的 A 工厂
步骤 3:B 成功注入 A 的半成品,完成初始化后放入一级缓存
步骤 4:回到 A 的创建流程,从一级缓存获取 B 的成品,注入 A 中
步骤 5:A 完成初始化,放入一级缓存,删除二级缓存的 A 半成品
我们可以看下关键源码,这段缓存查询逻辑:
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
     // 堆代码 duidaima.com
    // 先查一级缓存
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            // 再查二级缓存
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                // 最后查三级缓存,获取工厂并生成实例
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    singletonObject = singletonFactory.getObject(); // 生成半成品
                    this.earlySingletonObjects.put(beanName, singletonObject); // 放入二级缓存
                    this.singletonFactories.remove(beanName); // 移除三级缓存
                }
            }
        }
    }
    return singletonObject;
}
5. 为什么一定要三级缓存?二级缓存行不行?
、 有些伙伴可能会有疑问,直接用二级缓存存储半成品 Bean 不行吗?
原因是AOP 代理。如果 A 需要被代理,三级缓存的工厂会生成代理对象而非原始对象。假设直接存原始对象到二级缓存,后续代理会导致注入的对象不一致。通过工厂延迟生成代理对象,保证了循环依赖注入的是最终代理对象。
用户评论