java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Spring解决循环依赖

深入剖析Spring如何解决循环依赖

作者:北辰alk

循环依赖(Circular Dependency)是指两个或多个Bean相互依赖,形成一个闭环的情况,本文将和大家深入探讨一下Spring如何解决循环依赖,需要的可以参考下

一、什么是循环依赖

循环依赖(Circular Dependency)是指两个或多个Bean相互依赖,形成一个闭环的情况。例如:

@Service
public class AService {
    @Autowired
    private BService bService;
}

@Service
public class BService {
    @Autowired
    private AService aService;
}

上述代码中,AService依赖BService,而BService又依赖AService,形成了循环依赖。

二、Spring循环依赖的三种情况

构造器循环依赖:通过构造器注入形成的循环依赖,Spring无法解决,会直接抛出BeanCurrentlyInCreationException

Setter循环依赖(单例模式):通过setter方法注入形成的循环依赖,Spring可以解决
原型模式(prototype)循环依赖:scope为prototype的bean形成的循环依赖,Spring无法解决

三、Spring解决循环依赖的核心思想

Spring通过三级缓存机制来解决单例模式下的Setter循环依赖问题。核心思想是:

提前暴露未完全初始化的Bean实例:在Bean创建过程中,在完成实例化后、初始化前,将Bean的引用提前暴露

分级缓存:使用三级缓存存储不同状态的Bean,确保依赖注入时能获取到正确的引用

四、三级缓存详解

1. 三级缓存结构

/** 一级缓存:存放完全初始化好的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);

2. Bean创建与三级缓存交互流程

1.创建Bean实例:通过反射调用构造器创建Bean实例

2.放入三级缓存:将Bean包装成ObjectFactory并放入三级缓存singletonFactories

3.属性填充:填充Bean的属性,此时如果发现依赖其他Bean

4.初始化:执行初始化方法(@PostConstruct等)

5.放入一级缓存:将完全初始化好的Bean放入一级缓存,删除二、三级缓存中的记录

3. 循环依赖解决示例

以AService和BService的循环依赖为例:

1.开始创建AService

2.填充AService属性时发现需要BService

3.填充BService属性时发现需要AService

4.BService继续完成属性填充和初始化

5.BService创建完成,放入一级缓存

6.AService获得完全初始化的BService引用

7.AService继续完成属性填充和初始化

8.AService创建完成,放入一级缓存

五、源码分析

关键源码在DefaultSingletonBeanRegistry类中:

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 1. 先从一级缓存获取
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            // 2. 从二级缓存获取
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                // 3. 从三级缓存获取ObjectFactory
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    singletonObject = singletonFactory.getObject();
                    // 将三级缓存提升到二级缓存
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}

六、为什么构造器循环依赖无法解决

时序问题:构造器注入发生在实例化阶段,此时Bean尚未创建完成,无法提前暴露引用

缓存机制不适用:三级缓存机制依赖于在实例化后、初始化前暴露引用,而构造器注入时连实例都还没完全创建

七、为什么原型模式的循环依赖无法解决

生命周期不同:原型模式每次获取都创建新实例,Spring不缓存原型Bean

无法提前暴露引用:没有缓存机制支持,无法在创建过程中共享未完成初始化的引用

八、Spring解决循环依赖的局限性

只能解决单例模式的Setter注入循环依赖

大量使用循环依赖会导致代码结构混乱,增加维护难度

某些AOP场景下可能出现问题(需要特殊处理)

九、最佳实践

避免循环依赖:通过设计模式(如中介者模式、观察者模式)重构代码

使用Setter注入替代构造器注入:如果必须使用循环依赖

使用@Lazy注解:延迟加载依赖的Bean

@Service
public class AService {
    @Autowired
    @Lazy
    private BService bService;
}

使用ApplicationContextAware接口:手动获取依赖Bean

十、总结

Spring通过三级缓存机制巧妙地解决了单例模式下Setter注入的循环依赖问题,但其本质上是一种妥协方案。良好的系统设计应当尽量避免循环依赖,保持清晰的依赖关系。理解Spring解决循环依赖的机制,有助于我们更好地使用Spring框架,并在遇到相关问题时能够快速定位和解决。

到此这篇关于深入剖析Spring如何解决循环依赖的文章就介绍到这了,更多相关Spring解决循环依赖内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文