Spring 框架中的循环依赖处 理方式

[复制链接]
发表于 2025-3-18 12:05:11 | 显示全部楼层 |阅读模式

Spring 框架中的循环依赖是指两个或多个 Bean 之间相互依赖,导致它们在初始化时相互引用,从而形成一个闭环。例如,Bean A 依赖 Bean B,而 Bean B 又依赖 Bean A。如果不加以处理,循环依赖可能会导致 Spring 容器无法正常初始化。

Spring 通过一系列机制来处理循环依赖,以下是其处理原理的详细解析:


1. Spring 中的循环依赖类型

Spring 中的循环依赖分为三种类型:

(1)构造器循环依赖

构造器循环依赖是指两个或多个 Bean 通过构造器相互依赖。例如:

@Component
public class BeanA {
    private BeanB beanB;
    public BeanA(BeanB beanB) {
        this.beanB = beanB;
    }
}

@Component
public class BeanB {
    private BeanA beanA;
    public BeanB(BeanA beanA) {
        this.beanA = beanA;
    }
}

处理方式: Spring 不支持构造器循环依赖。因为构造器注入需要在 Bean 完全初始化之前完成,而循环依赖会导致 Spring 无法确定初始化顺序,从而抛出异常。

(2)单例 Bean 的 setter 循环依赖

单例 Bean 的 setter 循环依赖是指两个或多个单例 Bean 通过 setter 方法相互依赖。例如:

@Component
public class BeanA {
    private BeanB beanB;
    @Autowired
    public void setBeanB(BeanB beanB) {
        this.beanB = beanB;
    }
}

@Component
public class BeanB {
    private BeanA beanA;
    @Autowired
    public void setBeanA(BeanA beanA) {
        this.beanA = beanA;
    }
}

处理方式: Spring 支持单例 Bean 的 setter 循环依赖。通过三级缓存机制来解决。

(3)原型 Bean 的循环依赖

原型 Bean 的循环依赖是指两个或多个原型 Bean 通过 setter 方法相互依赖。例如:

@Component
@Scope("prototype")
public class BeanA {
    private BeanB beanB;
    @Autowired
    public void setBeanB(BeanB beanB) {
        this.beanB = beanB;
    }
}

@Component
@Scope("prototype")
public class BeanB {
    private BeanA beanA;
    @Autowired
    public void setBeanA(BeanA beanA) {
        this.beanA = beanA;
    }
}

处理方式: Spring 不支持原型 Bean 的循环依赖。因为原型 Bean 的生命周期由调用者管理,每次请求都会创建新的实例,Spring 无法保证循环依赖的正确性。


2. Spring 处理单例 Bean 循环依赖的三级缓存机制

Spring 使用三级缓存来解决单例 Bean 的 setter 循环依赖问题。三级缓存分别是:

(1)一级缓存:singletonObjects

一级缓存是一个 ConcurrentHashMap,用于存储完全初始化完成的单例 Bean。Bean 的初始化过程包括实例化、依赖注入和初始化方法调用。只有当 Bean 完全初始化完成后,才会被放入一级缓存。

(2)二级缓存:earlySingletonObjects

二级缓存是一个 ConcurrentHashMap,用于存储提前曝光的单例 Bean。当 Bean 还在初始化过程中,但已经被其他 Bean 需要时,Spring 会将 Bean 的一个早期引用放入二级缓存。这个早期引用是一个未完全初始化的 Bean 实例,但已经完成了实例化。

(3)三级缓存:singletonFactories

三级缓存是一个 ConcurrentHashMap,用于存储 Bean 的对象工厂(ObjectFactory)。当 Bean 还在初始化过程中,Spring 会将一个 ObjectFactory 放入三级缓存。ObjectFactory 是一个延迟加载的工厂,可以在需要时创建 Bean 的实例。


3. 单例 Bean 循环依赖的处理流程

假设我们有两个单例 Bean:BeanA 和 BeanB,它们通过 setter 方法相互依赖。以下是 Spring 处理循环依赖的流程:

(1)实例化 BeanA

Spring 开始实例化 BeanA,创建 BeanA 的实例。

(2)将 BeanA 放入三级缓存

Spring 将 BeanA 的 ObjectFactory 放入三级缓存 singletonFactories

(3)注入依赖

Spring 开始注入 BeanA 的依赖,发现 BeanA 依赖 BeanB。

(4)实例化 BeanB

Spring 开始实例化 BeanB,创建 BeanB 的实例。

(5)将 BeanB 放入三级缓存

Spring 将 BeanB 的 ObjectFactory 放入三级缓存 singletonFactories

(6)注入依赖(循环依赖出现)

Spring 开始注入 BeanB 的依赖,发现 BeanB 依赖 BeanA。此时,BeanA 还在初始化过程中,但已经在三级缓存中。

(7)从三级缓存获取 BeanA

Spring 从三级缓存 singletonFactories 中获取 BeanA 的 ObjectFactory,并调用 ObjectFactory.getObject() 方法获取 BeanA 的早期引用。

(8)将 BeanA 的早期引用放入二级缓存

Spring 将 BeanA 的早期引用放入二级缓存 earlySingletonObjects

(9)完成 BeanB 的依赖注入

Spring 使用 BeanA 的早期引用完成 BeanB 的依赖注入。

(10)完成 BeanB 的初始化

Spring 完成 BeanB 的初始化,将 BeanB 放入一级缓存 singletonObjects

(11)完成 BeanA 的依赖注入

Spring 使用 BeanB 的实例完成 BeanA 的依赖注入。

(12)完成 BeanA 的初始化

Spring 完成 BeanA 的初始化,将 BeanA 放入一级缓存 singletonObjects

(13)清理二级和三级缓存

Spring 清理二级缓存和三级缓存中与 BeanA 和 BeanB 相关的条目。


4. 总结

Spring 通过三级缓存机制解决了单例 Bean 的 setter 循环依赖问题。具体来说:

  • 一级缓存存储完全初始化完成的 Bean。
  • 二级缓存存储提前曝光的 Bean 的早期引用。
  • 三级缓存存储 Bean 的对象工厂,用于延迟加载。

通过这种机制,Spring 能够在 Bean 还在初始化过程中时,提供一个早期引用,从而解决循环依赖问题。然而,Spring 不支持构造器循环依赖和原型 Bean 的循环依赖,因为它们的处理逻辑更为复杂,且不符合 Spring 的设计原则。


希望以上内容对你理解 Spring 循环依赖的处理原理有所帮助!

GMT+8, 2025-9-5 18:29 , Processed in 0.101508 second(s), 35 queries Archiver|手机版|小黑屋|Attic ( 京ICP备2020048627号 )

快速回复 返回顶部 返回列表