问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

Spring框架如何优雅地处理循环依赖?

创作时间:
作者:
@小白创作中心

Spring框架如何优雅地处理循环依赖?

引用
CSDN
15
来源
1.
https://blog.csdn.net/u010013573/article/details/90573901
2.
https://www.jianshu.com/p/80495485984f
3.
https://blog.csdn.net/qq_33592535/article/details/107526306
4.
https://blog.csdn.net/qq_45591153/article/details/138345662
5.
https://blog.csdn.net/cy973071263/article/details/132676795
6.
https://cloud.baidu.com/article/3295972
7.
https://zhuanlan.zhihu.com/p/562691467
8.
https://blog.csdn.net/Jay_Chou345/article/details/123118708
9.
https://cloud.baidu.com/article/3295946
10.
https://cloud.baidu.com/article/3296101
11.
https://juejin.cn/post/7220962923735171132
12.
https://juejin.cn/post/7099745254743474212
13.
https://developer.aliyun.com/article/766880
14.
https://www.cnblogs.com/use-D/p/18290095
15.
https://cloud.tencent.com/developer/article/1497692

在Spring框架中,循环依赖(Circular Dependency)是指两个或多个Bean相互依赖,形成一个闭环。例如,Bean A依赖Bean B,而Bean B又依赖Bean A。这种情况下,如果处理不当,会导致程序无法正常启动或运行时错误。

Spring通过其强大的IoC容器和三级缓存机制,优雅地解决了循环依赖问题。本文将深入解析Spring如何处理循环依赖,以及不同注入方式对循环依赖的支持情况。

Spring的三级缓存机制

Spring的三级缓存机制是解决循环依赖的核心。这三级缓存分别是:

  1. singletonObjects:存放完全初始化好的单例Bean。
  2. earlySingletonObjects:存放提前暴露的半成品Bean(已实例化但未完成属性注入)。
  3. singletonFactories:存放Bean的工厂对象,用于生成半成品Bean。

当Spring创建一个Bean时,会按照以下步骤进行:

  1. 首先检查singletonObjects中是否存在该Bean的实例,如果存在则直接返回。
  2. 如果不存在,创建Bean的半成品对象,并将其添加到earlySingletonObjects缓存中。
  3. 注入该Bean所依赖的其他Bean。如果在注入过程中发现循环依赖,Spring会从earlySingletonObjects中获取半成品对象继续创建。
  4. 完成Bean的初始化后,将半成品对象转换为完整的Bean对象,并存储在singletonObjects缓存中。

这种机制确保了即使在循环依赖的情况下,Spring也能正确创建和初始化所有Bean。

不同注入方式对循环依赖的支持

Spring支持三种主要的依赖注入方式:构造函数注入、Setter注入和字段注入。它们在处理循环依赖时的表现有所不同。

构造函数注入

构造函数注入要求所有依赖在对象实例化时就必须完成注入。因此,当出现循环依赖时,Spring无法同时满足两个Bean的构造函数需求,导致抛出BeanCurrentlyInCreationException异常。

示例代码:

@Service
public class ServiceA {
    private final ServiceB serviceB;
    @Autowired
    public ServiceA(ServiceB serviceB) {
        this.serviceB = serviceB;
    }
}

@Service
public class ServiceB {
    private final ServiceA serviceA;
    @Autowired
    public ServiceB(ServiceA serviceA) {
        this.serviceA = serviceA;
    }
}

Setter注入

Setter注入允许依赖注入在实例化之后完成。Spring可以先创建半成品Bean,然后通过Setter方法逐步注入依赖,从而解决循环依赖问题。

示例代码:

@Service
public class ServiceA {
    private ServiceB serviceB;
    @Autowired
    public void setServiceB(ServiceB serviceB) {
        this.serviceB = serviceB;
    }
}

@Service
public class ServiceB {
    private ServiceA serviceA;
    @Autowired
    public void setServiceA(ServiceA serviceA) {
        this.serviceA = serviceA;
    }
}

字段注入

字段注入本质上也是属性注入的一种形式,与Setter注入类似,它允许Spring在实例化后通过反射机制注入依赖。因此,字段注入同样能够处理循环依赖。

示例代码:

@Service
public class ServiceA {
    @Autowired
    private ServiceB serviceB;
}

@Service
public class ServiceB {
    @Autowired
    private ServiceA serviceA;
}

如何避免循环依赖?

尽管Spring提供了多种解决循环依赖的方案,但最佳实践仍然是尽量避免循环依赖的出现。以下是一些常见的避免循环依赖的策略:

  1. 重构代码:重新设计类的职责划分,避免不必要的相互依赖。可以考虑将公共逻辑提取到第三个Bean中。

  2. 使用接口:通过接口隔离依赖,降低类之间的耦合度。

  3. 延迟加载:在循环依赖的Bean上使用@Lazy注解,让Spring延迟初始化该Bean,直到第一次实际使用时才创建。

  4. 重新思考设计:从根本上审视系统架构,确保模块间的职责清晰,避免过度耦合。

总结

Spring通过三级缓存机制和不同的依赖注入方式,提供了灵活的循环依赖解决方案。虽然Setter注入和字段注入能够处理循环依赖,但构造函数注入因其不可变性和线程安全性,仍然是推荐的首选方式。在实际开发中,应尽量避免循环依赖,保持代码的清晰和可维护性。

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号