Spring如何解决循环依赖问题?

Spring 通过三级缓存机制解决单例 Bean 的循环依赖问题,但仅适用于通过 setter/字段注入的循环依赖,构造器注入的循环依赖无法通过三级缓存解决

一、三级缓存结构

// Spring 中的三级缓存
public class DefaultSingletonBeanRegistry {
    // 一级缓存:存放完全初始化好的 Bean(成品)
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
    
    // 二级缓存:存放早期暴露的 Bean(半成品,已实例化但未初始化)
    private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
    
    // 三级缓存:存放 Bean 工厂,用于生成早期引用(可能创建代理对象)
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
}

二、解决流程(A 依赖 B,B 依赖 A)

1. 创建 A 的流程

// 1. 开始创建 A
// 2. 实例化 A(调用构造器),但未设置属性 → 得到一个"原始对象"
// 3. 将 A 的 ObjectFactory 放入三级缓存(singletonFactories)
// 4. 开始为 A 注入属性,发现依赖 B
// 5. 尝试从一级缓存获取 B(没有) → 开始创建 B

2. 创建 B 的流程

// 1. 实例化 B
// 2. 将 B 的 ObjectFactory 放入三级缓存
// 3. 为 B 注入属性,发现依赖 A
// 4. 查找 A 的流程:
//    - 一级缓存:无
//    - 二级缓存:无
//    - 三级缓存:找到 A 的 ObjectFactory
//    - 通过 ObjectFactory.getObject() 获取 A 的早期引用
//    - 将 A 从三级缓存移到二级缓存
// 5. B 成功注入 A 的早期引用
// 6. B 初始化完成,放入一级缓存

3. 完成 A 的创建

// 1. B 创建完成后,A 获得完整的 B
// 2. A 完成属性注入和初始化
// 3. 将 A 从二级缓存移除,放入一级缓存

三、关键设计

1. 三级缓存的作用

  • 一级缓存:完整的 Bean,可直接使用
  • 二级缓存:避免重复创建代理对象(性能优化)
  • 三级缓存:生成早期引用,支持 AOP 代理

2. 代码示例

@Component
public class ServiceA {
    @Autowired
    private ServiceB serviceB;  // setter/字段注入,支持循环依赖
}

@Component
public class ServiceB {
    @Autowired
    private ServiceA serviceA;  // 循环依赖
}

// 构造器注入无法解决循环依赖!
@Component
public class ServiceC {
    private final ServiceD serviceD;
    
    @Autowired
    public ServiceC(ServiceD serviceD) {  // 构造器注入,循环依赖会报错
        this.serviceD = serviceD;
    }
}

四、使用条件与限制

✅ 支持的情况

  • 必须是单例 Bean(scope=”singleton”)
  • 必须是Setter 注入字段注入(@Autowired)
  • 必须是非构造器注入

❌ 不支持的情况

// 1. 构造器注入的循环依赖(会抛出 BeanCurrentlyInCreationException)
@Bean
public A a(B b) { return new A(b); }

@Bean  
public B b(A a) { return new B(a); }

// 2. 原型(prototype)作用域的 Bean
@Component
@Scope("prototype")  // 原型 Bean 不支持循环依赖
public class PrototypeBean { ... }

// 3. @Async 注解的方法(因为需要代理)

五、源码关键方法

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 1. 从一级缓存获取
    Object singletonObject = this.singletonObjects.get(beanName);
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        // 2. 从二级缓存获取
        singletonObject = this.earlySingletonObjects.get(beanName);
        if (singletonObject == null && allowEarlyReference) {
            // 3. 从三级缓存获取 ObjectFactory
            ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
            if (singletonFactory != null) {
                // 4. 创建早期引用(可能生成代理)
                singletonObject = singletonFactory.getObject();
                // 5. 升级到二级缓存
                this.earlySingletonObjects.put(beanName, singletonObject);
                this.singletonFactories.remove(beanName);
            }
        }
    }
    return singletonObject;
}

六、实际开发建议

  1. 尽量避免循环依赖,这是代码设计问题
  2. 如果必须使用,确保:
    • 使用字段注入或 Setter 注入
    • Bean 是单例的
    • 考虑使用 @Lazy延迟加载
    @Component public class ServiceA { @Autowired @Lazy // 延迟注入,打破循环 private ServiceB serviceB; }
  3. 使用 ApplicationContext 手动获取 @Component public class ServiceA { @Autowired private ApplicationContext context; public void doSomething() { // 需要时才获取 ServiceB serviceB = context.getBean(ServiceB.class); } }

总结

Spring 通过三级缓存机制解决了大部分循环依赖问题,但这本质上是框架层面的妥协方案。良好的代码设计应该避免循环依赖,保持依赖关系的单向性。


作 者:南烛
链 接:https://www.itnotes.top/archives/903
来 源:IT笔记
文章版权归作者所有,转载请注明出处!


上一篇
下一篇