一、什么是循环依赖?

1.1 循环依赖的定义

循环依赖是指两个或多个Bean相互依赖,形成闭环引用关系。常见场景:

// 场景一:直接相互依赖
@Component
public class BeanA {
    @Autowired
    private BeanB b;
}

@Component
public class BeanB {
    @Autowired
    private BeanA a;
}

// 场景二:间接循环依赖
@Component
public class BeanA {
    @Autowired
    private BeanB b;
}

@Component  
public class BeanB {
    @Autowired
    private BeanC c;
}

@Component
public class BeanC {
    @Autowired
    private BeanA a;  // 形成A->B->C->A的循环
}

1.2 循环依赖的危害

如果没有特殊处理,循环依赖会导致:

  • 栈溢出:无限递归创建Bean

  • 死锁:多线程环境下可能发生

  • 启动失败:Spring容器无法正常初始化


二、手写实现:三级缓存解决循环依赖

2.1 简化版三级缓存实现

public class SimpleSpringContainer {
    
    // 一级缓存:完整Bean(单例池)
    private static Map<String, Object> singletonObjects = new ConcurrentHashMap<>();
    
    // 二级缓存:早期Bean(已实例化但未完全初始化)
    private static Map<String, Object> earlySingletonObjects = new HashMap<>();
    
    // 三级缓存:ObjectFactory(用于生成早期引用,支持AOP)
    private static Map<String, ObjectFactory> singletonFactories = new HashMap<>();
    
    // 正在创建的Bean集合
    private static Set<String> singletonsCurrentlyInCreation = new HashSet<>();
    
    // Bean定义缓存
    private static Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();
    
    // 获取Bean的核心方法
    public static Object getBean(String beanName) throws Exception {
        // 1. 先从缓存获取
        Object singleton = getSingleton(beanName);
        if (singleton != null) {
            return singleton;
        }
        
        // 2. 标记为正在创建
        if (!singletonsCurrentlyInCreation.contains(beanName)) {
            singletonsCurrentlyInCreation.add(beanName);
        }
        
        // 3. 实例化(调用构造方法)
        RootBeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
        Class<?> beanClass = beanDefinition.getBeanClass();
        Object instance = beanClass.newInstance(); // 简化:使用无参构造
        
        // 4. 添加到三级缓存(暴露早期引用)
        singletonFactories.put(beanName, () -> {
            // 这里可以插入AOP代理创建逻辑
            return instance; // 简化:直接返回原始对象
        });
        
        // 5. 属性填充(这里可能触发循环依赖)
        Field[] fields = beanClass.getDeclaredFields();
        for (Field field : fields) {
            if (field.isAnnotationPresent(Autowired.class)) {
                field.setAccessible(true);
                String dependencyName = field.getName(); // 简化:按字段名查找
                Object dependency = getBean(dependencyName); // 递归获取依赖
                field.set(instance, dependency);
            }
        }
        
        // 6. 初始化(执行init-method等)
        // ... 初始化逻辑
        
        // 7. 添加到一级缓存,清理其他缓存
        singletonObjects.put(beanName, instance);
        earlySingletonObjects.remove(beanName);
        singletonFactories.remove(beanName);
        
        return instance;
    }
    
    // 获取单例Bean
    private static Object getSingleton(String beanName) {
        // 1. 从一级缓存获取
        Object bean = singletonObjects.get(beanName);
        if (bean != null) {
            return bean;
        }
        
        // 2. 如果正在创建,检查二级缓存
        if (singletonsCurrentlyInCreation.contains(beanName)) {
            bean = earlySingletonObjects.get(beanName);
            
            // 3. 二级缓存没有,从三级缓存获取
            if (bean == null) {
                ObjectFactory factory = singletonFactories.get(beanName);
                if (factory != null) {
                    bean = factory.getObject(); // 可能返回代理对象
                    earlySingletonObjects.put(beanName, bean); // 升级到二级缓存
                    singletonFactories.remove(beanName); // 清理三级缓存
                }
            }
        }
        
        return bean;
    }
}

2.2 三级缓存的工作流程


三、为什么需要三级缓存?

3.1 缓存级别的分工

缓存级别 存储内容 作用 生命周期
一级缓存 完整的单例Bean 最终的Bean对象,可直接使用 容器生命周期内
二级缓存 早期Bean对象 已实例化但未完全初始化的Bean 解决循环依赖期间
三级缓存 ObjectFactory 生成早期引用的工厂,支持AOP 实例化后到初始化前

3.2 为什么需要二级缓存?

核心作用:分离成熟Bean和不完整Bean,保证并发安全。

问题场景:如果没有二级缓存

// 线程1:正在创建BeanA(属性还未填充)
// 线程2:同时请求BeanA

// 如果只有一级缓存:
// 线程2可能获取到不完整的BeanA(属性为null或未初始化)
// 要解决这个问题需要锁住整个getBean方法,性能极差

解决方案:二级缓存存储早期对象

  • 已创建但未初始化的Bean放入二级缓存

  • 其他线程可以从二级缓存获取早期引用

  • 完成初始化的Bean从二级缓存升级到一级缓存

  • 锁粒度更小,性能更高

3.3 为什么需要三级缓存?

核心作用:延迟AOP代理创建,保持Bean生命周期规范。

问题场景:如果只有二级缓存

// BeanA需要AOP代理
// 情况1:BeanA被循环依赖
// 如果在实例化后立即创建代理,破坏了Bean生命周期(代理应该在初始化后创建)

// 情况2:BeanA被多次循环依赖
// 可能创建多个代理对象,造成混乱

解决方案:三级缓存存储ObjectFactory

singletonFactories.put(beanName, () -> {
    // 这个lambda在需要时才执行
    return getEarlyBeanReference(beanName, bean);
});

优势

  1. 按需创建代理:只有发生循环依赖时才通过ObjectFactory创建代理

  2. 单例保证:多次获取都从同一个ObjectFactory获取,保证代理对象唯一

  3. 生命周期完整:普通Bean依然在初始化后创建代理


四、Spring源码深度解析

4.1 三级缓存在源码中的定义

// DefaultSingletonBeanRegistry.java
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry 
        implements SingletonBeanRegistry {
    
    // 一级缓存:完整单例Bean
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
    
    // 二级缓存:早期单例对象(已实例化但未初始化)
    private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
    
    // 三级缓存:单例工厂(用于创建早期引用)
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
    
    // 正在创建的单例Bean名称集合
    private final Set<String> singletonsCurrentlyInCreation = 
        Collections.newSetFromMap(new ConcurrentHashMap<>(16));
}

4.2 核心方法:getSingleton

// 5.3.10版本优化后的getSingleton方法
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 快速检查(无锁)
    Object singletonObject = this.singletonObjects.get(beanName);
    
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        singletonObject = this.earlySingletonObjects.get(beanName);
        
        if (singletonObject == null && allowEarlyReference) {
            synchronized (this.singletonObjects) {
                // 持有锁后再次检查
                singletonObject = this.singletonObjects.get(beanName);
                if (singletonObject == null) {
                    singletonObject = this.earlySingletonObjects.get(beanName);
                    if (singletonObject == null) {
                        // 从三级缓存获取ObjectFactory
                        ObjectFactory<?> singletonFactory = 
                            this.singletonFactories.get(beanName);
                        if (singletonFactory != null) {
                            // 创建早期引用(可能创建代理)
                            singletonObject = singletonFactory.getObject();
                            // 升级到二级缓存
                            this.earlySingletonObjects.put(beanName, singletonObject);
                            // 清理三级缓存
                            this.singletonFactories.remove(beanName);
                        }
                    }
                }
            }
        }
    }
    return singletonObject;
}

5.3.10版本优化

  1. 降低锁粒度:二级缓存检查放在锁外

  2. 避免死锁allowEarlyReference=false时不进入同步块

  3. 提升并发:减少锁竞争

4.3 循环依赖解决流程

步骤1:创建BeanA实例
// AbstractAutowireCapableBeanFactory.createBean()
protected Object createBean(String beanName, RootBeanDefinition mbd, Object[] args) 
        throws BeanCreationException {
    
    // 1. 实例化
    Object beanInstance = doCreateBean(beanName, mbd, args);
    
    // 2. 添加到三级缓存(在initializeBean之前)
    addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, beanInstance));
    
    // 3. 属性填充(可能触发循环依赖)
    populateBean(beanName, mbd, new BeanWrapperImpl(beanInstance));
    
    // 4. 初始化
    beanInstance = initializeBean(beanName, beanInstance, mbd);
    
    return beanInstance;
}

// 暴露早期引用
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    this.singletonFactories.put(beanName, singletonFactory);
    this.earlySingletonObjects.remove(beanName);
    this.singletonObjects.remove(beanName);
}
步骤2:BeanA依赖BeanB,触发循环
// populateBean中解析到@Autowired注解
// 调用beanFactory.getBean("beanB")

// BeanB的创建过程中又依赖BeanA
// 调用getSingleton("beanA")获取早期引用
步骤3:获取早期引用
// 对于AOP代理,getEarlyBeanReference会创建代理
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    
    // 应用SmartInstantiationAwareBeanPostProcessor
    for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAware) {
        exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
    }
    
    return exposedObject;
}

关键点AnnotationAwareAspectJAutoProxyCreator会在这里创建AOP代理。

步骤4:处理代理对象
// 初始化完成后检查是否需要替换为代理对象
if (earlySingletonExposure) {
    Object earlySingletonReference = getSingleton(beanName, false);
    if (earlySingletonReference != null) {
        // 如果初始化过程中对象没有改变(没有其他BeanPostProcessor修改)
        if (exposedObject == bean) {
            exposedObject = earlySingletonReference; // 使用早期代理对象
        }
        // 如果对象已被修改,抛出异常
        else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
            throw new BeanCurrentlyInCreationException(beanName, "Bean发生改变");
        }
    }
}

五、特殊情况分析

5.1 为什么不能解决构造器循环依赖?

@Component
public class BeanA {
    private BeanB b;
    
    @Autowired
    public BeanA(BeanB b) {  // 构造器注入
        this.b = b;
    }
}

@Component
public class BeanB {
    private BeanA a;
    
    @Autowired
    public BeanB(BeanA a) {  // 构造器注入
        this.a = a;
    }
}

原因分析

  1. 时机问题:构造器调用发生在实例化阶段,此时Bean还未创建

  2. 缓存未就绪:三级缓存在addSingletonFactory()中添加,而该方法在实例化之后才调用

  3. 无法获取引用:构造器需要完整对象,无法使用早期引用

错误流程

创建BeanA -> 调用构造器 -> 需要BeanB -> 创建BeanB -> 
调用构造器 -> 需要BeanA -> BeanA未放入缓存 -> 抛出异常

5.2 为什么多例Bean不能解决循环依赖?

@Component
@Scope("prototype")
public class PrototypeBeanA {
    @Autowired
    private PrototypeBeanB b;
}

@Component
@Scope("prototype")  
public class PrototypeBeanB {
    @Autowired
    private PrototypeBeanA a;
}

原因分析

  1. 无缓存机制:多例Bean每次getBean都创建新对象,不放入单例池

  2. 循环创建:A依赖B -> 创建B -> B依赖A -> 创建新A -> 新A依赖B -> 无限循环

5.3 AOP下的循环依赖注意事项

@Async导致的问题
@Component
public class ServiceA {
    @Autowired
    private ServiceB b;
    
    @Async  // 会在初始化后创建代理,改变Bean对象
    public void asyncMethod() {
        // ...
    }
}

@Component
public class ServiceB {
    @Autowired  
    private ServiceA a;  // 注入的是原始对象,不是代理
}

问题

  1. ServiceA早期引用(三级缓存)是原始对象

  2. ServiceB注入的是这个原始对象

  3. ServiceA初始化后,@Async会创建新代理对象

  4. ServiceB中的ServiceA引用与最终对象不一致

解决方案:使用@Lazy延迟注入

@Component
public class ServiceB {
    @Lazy
    @Autowired
    private ServiceA a;  // 注入代理,使用时再解析
}
@Transactional为什么没问题?
@Component
public class ServiceA {
    @Autowired
    private ServiceB b;
    
    @Transactional  // 事务代理
    public void transactionalMethod() {
        // ...
    }
}

原因:事务代理和AOP代理是同一个代理对象,在getEarlyBeanReference()中已创建。

5.4 Spring Boot 2.6+的循环依赖配置

# application.yml
spring:
  main:
    allow-circular-references: false  # 默认关闭循环依赖支持

关闭后如何解决

  1. 代码重构:消除循环依赖

  2. 使用@Lazy:延迟注入

  3. Setter注入:替换构造器注入

  4. 中间类:引入第三方协调依赖

// 使用@Lazy解决
@Component
public class BeanA {
    @Lazy
    @Autowired
    private BeanB b;  // 注入代理对象
}

@Component  
public class BeanB {
    @Autowired
    private BeanA a;
}

六、面试常见问题

Q1:Spring如何解决循环依赖?

:Spring通过三级缓存机制解决循环依赖:

  1. 一级缓存singletonObjects,存储完整的单例Bean

  2. 二级缓存earlySingletonObjects,存储早期Bean(已实例化未初始化)

  3. 三级缓存singletonFactories,存储ObjectFactory,用于创建早期引用(支持AOP代理)

Q2:为什么需要三级缓存,二级不行吗?

:需要三级缓存的主要原因:

  1. 支持AOP代理:在循环依赖时按需创建代理,保持Bean生命周期规范

  2. 保证代理单例:通过ObjectFactory保证多次获取的是同一个代理对象

  3. 分离职责:三级缓存负责创建,二级缓存负责存储,一级缓存负责最终结果

Q3:什么情况下循环依赖无法解决?

:以下情况无法解决:

  1. 构造器注入:实例化前无法获取依赖

  2. 多例Bean:无缓存机制

  3. @Async注解:初始化后创建新代理,导致引用不一致

  4. @Bean方法互相调用:非Spring管理的依赖

Q4:Spring Boot 2.6为什么默认关闭循环依赖?

:原因包括:

  1. 代码规范:循环依赖是糟糕的设计,应该避免

  2. 维护困难:循环依赖增加代码复杂度

  3. 性能考虑:三级缓存增加内存和CPU开销

  4. 未来兼容:Spring未来版本可能移除该特性

Q5:如何检测和避免循环依赖?

  1. 代码审查:检查相互依赖的类

  2. 架构设计:使用依赖倒置、单一职责原则

  3. 工具检测:使用SonarQube等代码质量工具

  4. 测试验证:编写启动测试验证无循环依赖


七、最佳实践

7.1 避免循环依赖的设计原则

  1. 单一职责原则:每个类只做一件事

  2. 依赖倒置原则:依赖抽象,不依赖具体

  3. 接口隔离原则:使用小接口而非大接口

  4. 层次化设计:明确依赖方向(上层依赖下层)

7.2 必须使用循环依赖时的建议

// 1. 使用@Lazy(推荐)
@Component
public class ServiceA {
    @Lazy
    @Autowired
    private ServiceB b;
}

// 2. 使用Setter注入代替构造器注入
@Component
public class ServiceA {
    private ServiceB b;
    
    @Autowired
    public void setB(ServiceB b) {
        this.b = b;
    }
}

// 3. 使用ApplicationContextAware
@Component
public class ServiceA implements ApplicationContextAware {
    private ApplicationContext context;
    
    @Override
    public void setApplicationContext(ApplicationContext context) {
        this.context = context;
    }
    
    public void method() {
        ServiceB b = context.getBean(ServiceB.class); // 使用时获取
    }
}

7.3 监控和调试

// 1. 添加循环依赖检测
@Component
public class CircularDependencyDetector implements BeanPostProcessor {
    
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        if (bean instanceof ApplicationContextAware) {
            // 可以记录Bean的依赖关系
        }
        return bean;
    }
}

// 2. 使用Spring Actuator监控
// application.yml
management:
  endpoints:
    web:
      exposure:
        include: beans
  endpoint:
    beans:
      enabled: true

八、总结

Spring的三级缓存机制是其IoC容器中最精妙的设计之一,它巧妙地解决了循环依赖这个经典难题。通过:

  1. 三级缓存分离:职责明确,性能优化

  2. ObjectFactory延迟创建:支持AOP等扩展

  3. 并发安全设计:多线程环境下稳定工作

  4. 灵活的配置选项:支持开启/关闭

然而,我们需要认识到:

  • 循环依赖是设计缺陷,应尽量避免

  • Spring的解决方案是妥协,不是鼓励

  • 理解原理比使用更重要,知其然知其所以然

Logo

立足具身智能前沿赛道,致力于搭建全球化、开源化、全栈式技术交流与实践共创平台。

更多推荐