Spring的循环依赖

1.引言

在Spring框架中,依赖注入(Dependency Injection, DI)是其核心功能之一,它通过配置来管理对象的创建和它们之间的依赖关系。然而,在复杂的应用程序中,开发人员有时会遇到循环依赖的问题,即Bean A依赖于Bean B,而Bean B又依赖于Bean A。如果不加以处理,这种情况会导致应用程序无法启动。

2. 什么是循环依赖?

循环依赖是指两个或多个Bean相互依赖,形成一个闭环。
例如,假设有两个Bean,Bean A和Bean B:

  • Bean A依赖于Bean B
  • Bean B依赖于Bean A

这种依赖关系就形成了一个循环,导致Spring容器在初始化Bean时无法确定哪个Bean应先创建。

3. Spring循环依赖的分类

根据依赖注入的方式不同,循环依赖可以分为以下几种类型:

3.1 构造器循环依赖

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

public class BeanA {
    private final BeanB beanB;

    public BeanA(BeanB beanB) {
        this.beanB = beanB;
    }
}

public class BeanB {
    private final BeanA beanA;

    public BeanB(BeanA beanA) {
        this.beanA = beanA;
    }
}
3.2 属性循环依赖

属性循环依赖是指两个或多个Bean通过属性相互依赖。例如:

public class BeanA {
    private BeanB beanB;

    public void setBeanB(BeanB beanB) {
        this.beanB = beanB;
    }
}

public class BeanB {
    private BeanA beanA;

    public void setBeanA(BeanA beanA) {
        this.beanA = beanA;
    }
}

4. Spring如何解决循环依赖

Spring框架通过三级缓存(三级缓存机制)来解决大多数情况下的循环依赖问题。三级缓存机制包括以下三个层次:

  • 单例池(singletonObjects):用于存放完全初始化好的单例Bean。
  • 早期曝光对象池(earlySingletonObjects):用于存放部分初始化完成的单例Bean,通常是通过提前暴露Bean引用来解决循环依赖。
  • 三级缓存(singletonFactories):用于存放Bean工厂,以便在需要时创建Bean实例。
4.1 三级缓存机制详解
4.1.1 单例池(singletonObjects)

单例池是一个Map,用于存放完全初始化好的单例Bean。当Spring容器创建一个Bean时,会首先检查单例池中是否已经存在该Bean,如果存在则直接返回,否则继续创建。

4.1.2 早期曝光对象池(earlySingletonObjects)

早期曝光对象池是一个Map,用于存放部分初始化完成的单例Bean。当Spring容器检测到循环依赖时,会将部分初始化完成的Bean提前放入该池中,以便其他Bean能够引用。

4.1.3 三级缓存(singletonFactories)

三级缓存是一个Map,用于存放Bean工厂。Bean工厂是一个用于创建Bean实例的对象,当需要创建Bean实例时,Spring容器会从三级缓存中获取相应的Bean工厂,并通过它来创建Bean实例。

4.2 三级缓存的工作流程

Spring容器创建Bean A,首先检查单例池中是否存在Bean A。
如果单例池中不存在Bean A,则检查早期曝光对象池中是否存在Bean A。
如果早期曝光对象池中也不存在Bean A,则从三级缓存中获取Bean A的工厂,并通过该工厂创建Bean A的实例。
创建Bean A实例的过程中,发现Bean A依赖于Bean B,因此开始创建Bean B。
创建Bean B的过程中,发现Bean B依赖于Bean A,此时检测到循环依赖。
将Bean A的实例放入早期曝光对象池中,以便Bean B可以引用。
继续完成Bean B的创建,并将其放入单例池中。
返回Bean B的实例,继续完成Bean A的创建,并将其放入单例池中。
通过上述流程,Spring容器可以成功处理大多数情况下的循环依赖。

5. 实践中的循环依赖

5.1 避免构造器循环依赖

构造器循环依赖是无法通过Spring的三级缓存机制解决的,因为构造器循环依赖会导致Spring无法实例化任何一个Bean。解决这种问题的方法有:

  • 重构代码,避免循环依赖。
  • 使用Setter方法注入而不是构造器注入。
5.2 使用@Lazy注解

在某些情况下,可以使用@Lazy注解来延迟Bean的初始化,从而避免循环依赖。例如:

public class BeanA {
    @Autowired
    @Lazy
    private BeanB beanB;
}

public class BeanB {
    @Autowired
    private BeanA beanA;
}
5.3 使用代理对象

使用代理对象也是解决循环依赖的一种方法。Spring AOP(面向切面编程)通过动态代理机制创建Bean的代理对象,可以在一定程度上缓解循环依赖的问题。

6. Spring循环依赖的潜在问题

尽管Spring可以通过三级缓存机制解决大多数情况下的循环依赖,但在实际开发中,循环依赖仍可能导致一些潜在的问题:

  • 代码难以维护:循环依赖会使代码逻辑复杂,增加代码的维护难度。
  • 性能问题:频繁使用三级缓存可能会影响性能,特别是在Bean数量较多的情况下。
  • 潜在的内存泄漏:不正确的依赖管理可能导致内存泄漏,从而影响应用程序的稳定性。

7. 总结

Spring循环依赖是一个复杂的问题,理解其工作原理和解决机制对于开发高质量的Spring应用程序至关重要。通过合理的设计和最佳实践,可以有效避免和解决循环依赖,确保应用程序的稳定性和可维护性。

三级缓存:
singletonObjects:缓存某个 beanName 对应的经过了完整生命周期的bean;
earlySingletonObjects:缓存提前拿原始对象进行了 AOP 之后得到的代理对象,原始对象还没有进行属性注入和后续的 BeanPostProcessor等生命周期;
singletonFactories:缓存的是一个 ObjectFactory ,主要用来去生成原始对象进行了 AOP之后得到的代理对象,在每个 Bean 的生成过程中,都会提前暴露一个工厂,这个工厂可能用到,也可能用不到,如果没有出现循环依赖依赖本 bean,那么这个工厂无用,本 bean 按照自己的生命周期执行,执行完后直接把本 bean 放入 singletonObjects 中即可,如果出现了循环依赖依赖了本 bean,则另外那个 bean 执行 ObjectFactory 提交得到一个 AOP 之后的代理对象(如果有 AOP 的话,如果无需 AOP ,则直接得到一个原始对象)。

  • 14
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值