Skip to content

循环依赖处理 ​

深入理解Spring如何处理循环依赖,掌握三级缓存机制的原理,理解提前暴露、早期引用的创建、代理对象的循环依赖处理等核心机制。

📋 循环依赖概览 ​

循环依赖是指两个或多个Bean相互依赖,形成一个依赖环。Spring通过三级缓存机制巧妙地解决了单例Bean的循环依赖问题。

🎯 一、循环依赖场景 ​

1.1 常见循环依赖场景 ​

场景1:构造函数循环依赖

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

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

场景2:Setter循环依赖

java
@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;
    }
}

场景3:字段循环依赖

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

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

1.2 循环依赖的支持情况 ​

依赖方式单例Bean原型Bean
构造器注入❌ 不支持❌ 不支持
Setter/字段注入✅ 支持❌ 不支持

为什么构造器注入不支持循环依赖?

构造器在实例化阶段就必须完成依赖注入,此时Bean还未完全创建,无法提前暴露,因此无法解决循环依赖。

🎯 二、三级缓存机制 ​

2.1 三级缓存定义 ​

Spring使用三个Map来解决循环依赖:

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

三级缓存的作用:

  • 一级缓存(singletonObjects):存储完全初始化好的Bean,可以直接使用
  • 二级缓存(earlySingletonObjects):存储早期Bean(已实例化但未初始化),用于解决循环依赖
  • 三级缓存(singletonFactories):存储Bean工厂,用于创建早期引用

2.2 三级缓存流程图 ​

🎯 三、循环依赖解决流程 ​

3.1 getSingleton方法 ​

getSingleton方法是获取单例Bean的核心方法,也是解决循环依赖的关键:

java
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<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    // 4. 执行工厂方法,获取早期引用
                    singletonObject = singletonFactory.getObject();
                    // 5. 将早期引用放入二级缓存
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    // 6. 从三级缓存删除工厂
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return singletonObject;
}

执行流程:

  1. 首先从一级缓存获取完全初始化好的Bean
  2. 如果一级缓存没有且Bean正在创建中,从二级缓存获取早期Bean
  3. 如果二级缓存也没有且允许早期引用,从三级缓存获取工厂
  4. 执行工厂方法获取早期引用,放入二级缓存,删除三级缓存的工厂
  5. 返回早期引用

3.2 addSingletonFactory方法 ​

在Bean实例化后、属性注入前,将Bean的工厂放入三级缓存:

java
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(singletonFactory, "Singleton factory must not be null");
    synchronized (this.singletonObjects) {
        if (!this.singletonObjects.containsKey(beanName)) {
            this.singletonFactories.put(beanName, singletonFactory);
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }
}

3.3 完整流程示例 ​

场景:ServiceA依赖ServiceB,ServiceB依赖ServiceA

步骤1:创建ServiceA

java
// 1. getBean("serviceA")
// 2. 从缓存获取(一级、二级、三级都没有)
// 3. 标记serviceA正在创建:singletonsCurrentlyInCreation.add("serviceA")
// 4. 实例化ServiceA(调用构造器)
// 5. 提前暴露:addSingletonFactory("serviceA", () -> getEarlyBeanReference(...))
//    将serviceA的工厂放入三级缓存
// 6. 填充属性,发现需要serviceB

步骤2:创建ServiceB

java
// 1. getBean("serviceB")
// 2. 从缓存获取(一级、二级、三级都没有)
// 3. 标记serviceB正在创建:singletonsCurrentlyInCreation.add("serviceB")
// 4. 实例化ServiceB(调用构造器)
// 5. 提前暴露:addSingletonFactory("serviceB", () -> getEarlyBeanReference(...))
//    将serviceB的工厂放入三级缓存
// 6. 填充属性,发现需要serviceA
// 7. getBean("serviceA")
// 8. getSingleton("serviceA", true):
//    - 一级缓存没有
//    - serviceA正在创建中
//    - 从三级缓存获取serviceA的工厂
//    - 执行工厂方法获取早期serviceA(可能返回代理对象)
//    - 将早期serviceA放入二级缓存
//    - 从三级缓存删除serviceA的工厂
//    - 返回早期serviceA
// 9. 将早期serviceA注入到serviceB
// 10. 完成serviceB的初始化
// 11. addSingleton("serviceB", serviceB):将serviceB放入一级缓存,删除二级、三级缓存
// 12. 返回serviceB

步骤3:完成ServiceA的创建

java
// 1. 将serviceB注入到serviceA
// 2. 完成serviceA的初始化
// 3. addSingleton("serviceA", serviceA):
//    - 将serviceA放入一级缓存
//    - 从二级缓存删除serviceA(如果存在)
//    - 从三级缓存删除serviceA的工厂(如果存在)
// 4. 返回serviceA

🎯 四、代理对象的循环依赖 ​

4.1 代理对象的处理 ​

如果Bean需要被AOP代理,循环依赖的处理会更加复杂。

getEarlyBeanReference方法:

java
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
        for (BeanPostProcessor bp : getBeanPostProcessors()) {
            if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
                SmartInstantiationAwareBeanPostProcessor ibp = 
                        (SmartInstantiationAwareBeanPostProcessor) bp;
                exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
            }
        }
    }
    return exposedObject;
}

AbstractAutoProxyCreator.getEarlyBeanReference:

java
@Override
public Object getEarlyBeanReference(Object bean, String beanName) {
    Object cacheKey = getCacheKey(bean.getClass(), beanName);
    this.earlyProxyReferences.put(cacheKey, bean);
    return wrapIfNecessary(bean, beanName, cacheKey);
}

处理流程:

  1. 在提前暴露时,如果Bean需要被代理,调用getEarlyBeanReference创建代理对象
  2. 代理对象放入二级缓存,供其他Bean注入
  3. 在Bean初始化完成后,检查是否需要重新创建代理
  4. 如果已经创建过早期代理,直接返回该代理,不再重复创建

4.2 代理对象循环依赖示例 ​

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

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

处理流程:

  1. 创建ServiceA实例
  2. 提前暴露时,创建ServiceA的代理对象(因为@Transactional)
  3. ServiceA代理对象放入二级缓存
  4. 创建ServiceB实例
  5. ServiceB注入ServiceA时,从二级缓存获取ServiceA代理对象
  6. 提前暴露时,创建ServiceB的代理对象
  7. ServiceB代理对象放入二级缓存
  8. ServiceA注入ServiceB时,从二级缓存获取ServiceB代理对象
  9. 完成初始化,将代理对象放入一级缓存

🎯 五、为什么原型Bean不支持循环依赖 ​

5.1 原型Bean的特点 ​

  • 每次getBean都会创建新实例
  • 不会放入单例缓存
  • 生命周期由调用方管理

5.2 为什么不支持 ​

原因分析:

  1. 无法缓存:原型Bean不会放入单例缓存,无法提前暴露
  2. 无限递归:如果支持,每次创建都会触发依赖的创建,导致无限递归
  3. 内存泄漏:无法追踪正在创建的原型Bean,可能导致内存泄漏

Spring的检查:

java
protected void beforePrototypeCreation(String beanName) {
    Object curVal = this.prototypesCurrentlyInCreation.get();
    if (curVal == null) {
        this.prototypesCurrentlyInCreation.set(beanName);
    }
    else if (curVal instanceof String) {
        Set<String> beanNameSet = new HashSet<>(2);
        beanNameSet.add((String) curVal);
        beanNameSet.add(beanName);
        this.prototypesCurrentlyInCreation.set(beanNameSet);
    }
    else {
        Set<String> beanNameSet = (Set<String>) curVal;
        if (beanNameSet.contains(beanName)) {
            throw new BeanCurrentlyInCreationException(beanName);
        }
        beanNameSet.add(beanName);
    }
}

如果检测到原型Bean的循环依赖,会抛出BeanCurrentlyInCreationException异常。

📖 学习资源 ​

视频教程 ​

书籍推荐 ​

  • 《Spring源码深度解析》 - 郝佳
  • 《Spring技术内幕》 - 计文柯

网站资源 ​

✅ 学习检查清单 ​

  • [ ] 已理解循环依赖的概念和场景
  • [ ] 已理解三级缓存机制的作用
  • [ ] 已理解getSingleton方法的执行流程
  • [ ] 已理解提前暴露的原理
  • [ ] 已理解代理对象的循环依赖处理
  • [ ] 已理解为什么构造器注入不支持循环依赖
  • [ ] 已理解为什么原型Bean不支持循环依赖
  • [ ] 已绘制循环依赖解决流程图
  • [ ] 已总结核心知识点

上一章节:Bean生命周期
下一章节:设计模式应用
返回目录:Spring源码深度解析