实战:Java 循环依赖&三级缓存-源码全解析
·
Java 循环依赖、Bean 作用域与 AOP 代理问题全解析(Java 8 环境)
目录
- 循环依赖核心逻辑、源码与 Java 8 解决方案
- 多例 Bean 定义、声明方式与 Spring 单例 Bean 实现机制
- 三级缓存解决循环依赖的实例与源码拆解
- AOP 动态代理与循环依赖的冲突问题及解决方案
一、循环依赖核心逻辑、源码与 Java 8 解决方案
1. 循环依赖的核心定义
循环依赖指两个或多个 Bean 相互依赖形成闭环(如 A→B→C→A),导致 Spring 容器无法正常初始化 Bean 的问题。核心前提:循环依赖仅存在于「构造器注入」或「多例 Bean」场景,Spring 默认的「单例+setter 注入」能自动解决循环依赖。
2. 底层核心逻辑:Spring Bean 初始化流程与循环依赖根源
(1)单例 Bean 初始化核心流程(简化)
1. 入口:AbstractBeanFactory#getBean() → doGetBean()
2. 检查缓存:先查三级缓存(singletonObjects、earlySingletonObjects、singletonFactories)
3. 缓存未命中:执行 createBean() → doCreateBean()
4. 实例化:通过反射创建 Bean 原始对象(未注入属性、未执行初始化方法)
5. 暴露半成品 Bean:将原始对象包装为「对象工厂」存入三级缓存
6. 填充属性:注入依赖的 Bean(若依赖未创建,递归调用 getBean())
7. 初始化:执行 @PostConstruct、InitializingBean 等
8. 存入单例池:将完整 Bean 存入一级缓存,移除二、三级缓存
(2)循环依赖的根源:初始化顺序闭环
- 正常场景(Spring 自动解决):
场景:A→B、B→A(单例+Setter注入,无AOP代理)
- 容器创建A:实例化A(A@123,半成品)→ 存入三级缓存(
singletonFactories={"a": ObjectFactory(A@123)})→ 填充属性需要B; - 容器创建B:实例化B(B@456,半成品)→ 存入三级缓存(
singletonFactories={"a": ObjectFactory(A@123), "b": ObjectFactory(B@456)})→ 填充属性需要A; - B获取A:调用
getBean("a"),触发缓存查询:- 先查一级缓存(singletonObjects):A不存在;
- 再查二级缓存(earlySingletonObjects):A不存在;
- 最后查三级缓存(singletonFactories):获取A的ObjectFactory;
- 调用ObjectFactory.getObject(),得到A的半成品(A@123);
- 关键一步:将A@123存入二级缓存(
earlySingletonObjects={"a": A@123}),同时从三级缓存移除A(singletonFactories移除"a");
- B注入A@123:完成属性填充→执行初始化→存入一级缓存(
singletonObjects={"b": B@456}); - A获取B:从一级缓存拿到B@456,完成属性填充→执行初始化;
- A存入一级缓存:调用
addSingleton("a", A@123),将A@123存入一级缓存,同时移除二级缓存中的A(earlySingletonObjects移除"a"); - 最终缓存状态:一级缓存
{"a": A@123, "b": B@456},二、三级缓存空。
3. 源码解析:三级缓存解决单例 setter 注入循环依赖
(1)三级缓存核心变量(DefaultSingletonBeanRegistry.java)
// 一级缓存:存储完全初始化完成的单例 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);
// 正在创建中的 Bean 名称集合(标记递归创建中的 Bean)
private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16));
(2)核心流程源码拆解
- 标记 Bean 为“正在创建”(doGetBean()):
protected <T> T doGetBean(...) {
Object sharedInstance = getSingleton(beanName); // 先查缓存
if (sharedInstance != null) return (T) beanInstance;
// 标记为正在创建
if (!singletonsCurrentlyInCreation.contains(beanName)) {
singletonsCurrentlyInCreation.add(beanName);
}
try {
RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
sharedInstance = createBean(beanName, mbd, args); // 创建 Bean
} finally {
// 创建完成后移除标记
if (singletonsCurrentlyInCreation.contains(beanName)) {
singletonsCurrentlyInCreation.remove(beanName);
}
}
return (T) beanInstance;
}
- 实例化后暴露半成品到三级缓存(doCreateBean()):
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
// 1. 实例化 Bean(原始对象,未注入属性)
BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args);
final Object bean = instanceWrapper.getWrappedInstance();
// 2. 单例+允许循环依赖,暴露半成品到三级缓存
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
// 3. 填充属性(注入依赖,递归 getBean())
populateBean(beanName, mbd, instanceWrapper);
// 4. 初始化(@PostConstruct、InitializingBean 等)
exposedObject = initializeBean(beanName, exposedObject, mbd);
// 5. 存入一级缓存
addSingleton(beanName, exposedObject);
return exposedObject;
}
- 三级缓存→二级缓存转换(getSingleton()):
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
// 1. 查一级缓存
Object singletonObject = this.singletonObjects.get(beanName);
// 2. 一级未命中且 Bean 正在创建中
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
synchronized (this.singletonObjects) {
// 3. 查二级缓存
singletonObject = this.earlySingletonObjects.get(beanName);
// 4. 二级未命中,从三级缓存获取工厂生成半成品
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
// 存入二级缓存,移除三级缓存
this.earlySingletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
}
}
}
}
return singletonObject;
}
4. Java 8 环境循环依赖解决方法(按优先级)
(1)首选:单例 Bean + Setter 注入(Spring 自动解决)
@Component
public class A {
private B b;
@Autowired
public void setB(B b) { this.b = b; }
}
@Component
public class B {
private A a;
@Autowired
public void setA(A a) { this.a = a; }
}
(2)构造器注入场景:@Lazy 延迟加载
@Component
public class A {
private B b;
@Autowired
public A(@Lazy B b) { this.b = b; } // 延迟初始化 B
}
@Component
public class B {
private A a;
@Autowired
public B(@Lazy A a) { this.a = a; } // 延迟初始化 A
}
(3)手动解耦:第三方服务打破循环
// 第三方服务抽取共同逻辑
@Component
public class CommonService {
public void doCommon() { /* 共同逻辑 */ }
}
@Component
public class A {
private CommonService commonService;
@Autowired
public A(CommonService commonService) { this.commonService = commonService; }
}
@Component
public class B {
private CommonService commonService;
@Autowired
public B(CommonService commonService) { this.commonService = commonService; }
}
(4)手动注入:实现 ApplicationContextAware
@Component
public class A implements ApplicationContextAware {
private B b;
@Override
public void setApplicationContext(ApplicationContext ctx) throws BeansException {
this.b = ctx.getBean(B.class); // 手动获取依赖
}
}
(5)多例循环:改用单例或 Provider 延迟获取
import javax.inject.Provider;
@Component
@Scope("prototype")
public class A {
private Provider<B> bProvider; // 注入 Provider
@Autowired
public A(Provider<B> bProvider) { this.bProvider = bProvider; }
public void useB() {
B b = bProvider.get(); // 每次 get() 新建多例实例
}
}
(6)应急方案:禁用循环依赖检查(不推荐)
spring:
main:
allow-circular-references: false
二、多例 Bean 定义、声明方式与 Spring 单例 Bean 实现机制
1. 多例 Bean 的核心定义与特性
(1)定义
多例(Prototype)Bean:每次通过 getBean() 获取时,都会创建全新实例,容器仅负责创建,不负责销毁,生命周期由使用者管理。
(2)单例 vs 多例核心区别
| 特性 | 单例 Bean(默认) | 多例 Bean(Prototype) |
|---|---|---|
| 实例数量 | 容器中仅 1 个实例 | 每次获取都新建实例 |
| 容器管理 | 启动时创建(懒加载除外),销毁时回收 | 获取时创建,容器不销毁实例 |
| 循环依赖支持 | 支持(三级缓存) | 不支持(无缓存,递归创建报错) |
| 适用场景 | 无状态服务(工具类、Service) | 有状态对象(请求级数据载体) |
(3)注意
Spring 其他作用域(request、session)并非严格多例,但非容器全局唯一,可理解为「限定范围的多例」,通常说的「多例」特指 prototype 作用域。
2. 多例 Bean 的声明方式(Java 8 支持)
(1)注解方式:@Scope(“prototype”)(推荐)
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) // 常量定义,避免拼写错误
public class PrototypeBean {
public PrototypeBean() {
System.out.println("多例 Bean 创建:" + this);
}
}
(2)XML 配置方式:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="prototypeBean" class="com.example.PrototypeBean" scope="prototype"/>
</beans>
(3)Java 配置类方式:@Bean + @Scope
@Configuration
public class BeanConfig {
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public PrototypeBean prototypeBean() {
return new PrototypeBean();
}
}
(4)多例 Bean 特殊注意事项
多例 Bean 被单例 Bean 依赖时,需用 Provider 确保每次使用都新建:
@Component // 单例 Bean
public class SingletonBean {
@Autowired
private Provider<PrototypeBean> prototypeBeanProvider;
public void usePrototypeBean() {
PrototypeBean bean = prototypeBeanProvider.get(); // 每次获取新实例
}
}
3. Spring 单例 Bean 的创建与单例保证机制
(1)单例 Bean 创建核心流程
getBean(beanName) → doGetBean() → 检查缓存 → 缓存未命中则创建 Bean → 存入缓存
(2)创建时机
- 默认:容器启动时创建(ApplicationContext 初始化时,触发
finishBeanFactoryInitialization()) - 懒加载:添加
@Lazy注解,首次 getBean() 时创建
(3)单例保证的 3 大核心机制
-
一级缓存:存储唯一实例
- 核心变量:
singletonObjects(ConcurrentHashMap,线程安全) - 作用:所有 getBean() 先查该缓存,命中直接返回,确保实例唯一
- 核心变量:
-
创建锁:避免并发创建
- 双重检查锁 + synchronized 同步锁(
synchronized (this.singletonObjects)) singletonsCurrentlyInCreation集合标记正在创建的 Bean,避免重复创建
- 双重检查锁 + synchronized 同步锁(
-
创建后存入缓存
Bean 初始化完成后,通过addSingleton()存入一级缓存,后续复用:
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
this.singletonObjects.put(beanName, singletonObject); // 存入一级缓存
this.singletonFactories.remove(beanName); // 移除三级缓存
this.earlySingletonObjects.remove(beanName); // 移除二级缓存
this.registeredSingletons.add(beanName);
}
}
三、三级缓存解决循环依赖的实例与源码拆解
1. 场景准备:循环依赖 Bean 定义
// A 依赖 B,Setter 注入
@Component
public class A {
private B b;
@Autowired
public void setB(B b) {
this.b = b;
System.out.println("A 注入 B 成功:" + b);
}
}
// B 依赖 A,Setter 注入
@Component
public class B {
private A a;
@Autowired
public void setA(A a) {
this.a = a;
System.out.println("B 注入 A 成功:" + a);
}
}
2. 三级缓存核心变量(DefaultSingletonBeanRegistry)
// 一级缓存:完整单例 Bean
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
// 二级缓存:半成品 Bean
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);
// 三级缓存:Bean 工厂(包装半成品)
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
// 正在创建中的 Bean 集合
private final Set<String> singletonsCurrentlyInCreation = Collections.newSetFromMap(new ConcurrentHashMap<>(16));
3. 完整流程拆解(8 个关键步骤)
步骤 1:容器创建 A,触发 getBean(“a”) → doGetBean()
- 检查缓存:A 不存在,标记 A 为「正在创建」(
singletonsCurrentlyInCreation={"a"}) - 调用
createBean()创建 A
步骤 2:创建 A 实例,暴露到三级缓存(doCreateBean())
- 实例化 A:生成原始对象
A@123(半成品) - 满足条件(单例+允许循环依赖),调用
addSingletonFactory()存入三级缓存:addSingletonFactory("a", () -> getEarlyBeanReference("a", mbd, A@123)); - 缓存状态:
- 一级:空;二级:空;三级:
{"a"→ObjectFactory(A@123)};正在创建:{"a"}
- 一级:空;二级:空;三级:
步骤 3:A 注入 B,触发 getBean(“b”) → 创建 B
- 重复步骤 1-2:标记 B 为「正在创建」(
singletonsCurrentlyInCreation={"a","b"}) - 实例化 B:生成
B@456(半成品),存入三级缓存:{"b"→ObjectFactory(B@456)} - 缓存状态:
- 一级:空;二级:空;三级:
{"a"→ObjectFactory(A@123), "b"→ObjectFactory(B@456)};正在创建:{"a","b"}
- 一级:空;二级:空;三级:
步骤 4:B 注入 A,从三级缓存获取 A 半成品(getSingleton(“a”))
- 查一级缓存:A 不存在;查二级缓存:A 不存在
- 查三级缓存:获取 A 的 ObjectFactory,调用
getObject()得到A@123(半成品) - 将
A@123存入二级缓存,移除三级缓存中 A 的记录 - 缓存状态:
- 一级:空;二级:
{"a"→A@123};三级:{"b"→ObjectFactory(B@456)};正在创建:{"a","b"}
- 一级:空;二级:
步骤 5:B 注入 A 完成,B 初始化并存入一级缓存
- B 执行
setA(A@123),完成属性注入 - B 执行初始化(@PostConstruct 等),成为完整 Bean
B@456 - 调用
addSingleton("b", B@456)存入一级缓存 - 缓存状态:
- 一级:
{"b"→B@456};二级:{"a"→A@123};三级:空;正在创建:{"a"}
- 一级:
步骤 6:A 注入 B 完成(从一级缓存获取 B)
- A 从一级缓存获取
B@456,执行setB(B@456)完成属性注入 - 打印:
A 注入 B 成功:B@456
步骤 7:A 执行初始化,成为完整 Bean
- A 执行
initializeBean()(@PostConstruct、InitializingBean 等),成为完整 BeanA@123
步骤 8:A 存入一级缓存,移除二、三级缓存
- 调用
addSingleton("a", A@123)存入一级缓存 - 最终缓存状态:
- 一级:
{"a"→A@123, "b"→B@456};二级:空;三级:空;正在创建:空
- 一级:
4. 三级缓存核心作用总结
| 步骤 | 缓存操作 | 核心目的 |
|---|---|---|
| 2 | A 半成品存入三级缓存 | 提前暴露 A,让依赖 A 的 B 能获取半成品 |
| 4 | A 半成品从三级→二级缓存 | 避免重复生成半成品,确保早期实例唯一 |
| 5 | B 完整实例存入一级缓存 | 供 A 注入使用,完成 B 的初始化闭环 |
| 8 | A 完整实例存入一级缓存 | 保证单例复用,提供最终可用 Bean |
四、AOP 动态代理与循环依赖的冲突问题及解决方案
1. AOP+循环依赖报错场景(Java 8 环境)
(1)报错条件(需同时满足)
- 循环依赖双方都需要 AOP 代理(如都加
@Transactional、@Async) - 依赖注入方式为字段注入或 setter 注入
(2)报错示例
@Component
@Transactional // A 需要 AOP 代理
public class A {
@Autowired
private B b; // 字段注入 B
public void doA() { b.doB(); }
}
@Component
@Transactional // B 需要 AOP 代理
public class B {
@Autowired
private A a; // 字段注入 A
public void doB() { a.doA(); }
}
(3)报错信息
org.springframework.beans.factory.BeanCurrentlyInCreationException:
Error creating bean with name 'a': Bean with name 'a' has been injected into other beans [...b...] in its raw version as part of a circular reference, but has eventually been wrapped.
2. 底层原因:AOP 代理生成时机与三级缓存冲突
(1)关键前提
- AOP 代理默认生成时机:Bean 初始化完成后(
initializeBean()阶段,通过postProcessAfterInitialization()) - 三级缓存逻辑:循环依赖时提前暴露 Bean 原始对象,后续可能提前生成代理对象
(2)冲突流程
- A 实例化后暴露原始对象到三级缓存,A 注入 B 触发 B 创建
- B 依赖 A,从三级缓存获取 A 的原始对象,提前生成 A 的代理
A@proxy并注入 B - B 初始化完成,生成自己的代理
B@proxy存入一级缓存 - A 继续初始化,再次生成 A 的代理
A@proxy2(与A@proxy是不同实例) - Spring 发现早期暴露的
A@proxy与最终代理A@proxy2不一致,抛出异常
(3)源码冲突触发点(AbstractAutowireCapableBeanFactory.java)
if (earlySingletonExposure) {
Object earlySingletonReference = getSingleton(beanName, false); // 获取早期代理 A@proxy
if (earlySingletonReference != null) {
if (exposedObject == bean) { // exposedObject 是 A@proxy2,bean 是 A@raw
exposedObject = earlySingletonReference;
} else if (!this.allowRawInjectionDespiteWrapping && hasDependentBean(beanName)) {
// 触发报错:早期对象与最终对象不一致
throw new BeanCurrentlyInCreationException(beanName, ...);
}
}
}
3. Java 8 环境解决方案(按优先级)
(1)方案 1:@Lazy 延迟加载(最简单,无侵入)
@Component
@Transactional
public class A {
@Autowired
@Lazy // 延迟 B 的代理生成
private B b;
public void doA() { b.doB(); }
}
@Component
@Transactional
public class B {
@Autowired
private A a;
public void doB() { a.doA(); }
}
- 原理:
@Lazy生成代理占位符注入 A,A 初始化时不创建 B 的代理,首次调用时才生成,避免冲突
(2)方案 2:开启 allowRawInjectionDespiteWrapping(全局配置)
@Configuration
public class AopCycleConfig {
@Bean
public BeanFactoryPostProcessor beanFactoryPostProcessor(ConfigurableBeanFactory beanFactory) {
return beanFactory1 -> {
// 允许原始对象注入(关闭一致性校验)
beanFactory.setAllowRawInjectionDespiteWrapping(true);
};
}
}
- 优点:零代码侵入,快速解决
- 风险:若业务依赖代理对象,可能出现切面不生效问题
(3)方案 3:配置 AOP 代理生成策略
@Configuration
// proxyTargetClass=true:强制 CGLIB 代理;exposeProxy=true:暴露代理到 ThreadLocal
@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)
public class AopConfig {
}
// 配合 AopContext 获取代理对象(解决自调用问题)
@Component
@Transactional
public class A {
@Autowired
private B b;
public void doA() {
((A) AopContext.currentProxy()).doASub(); // 自调用时获取代理对象
b.doB();
}
@Transactional
public void doASub() { /* 切面逻辑 */ }
}
- 原理:CGLIB 代理生成逻辑稳定,提前暴露的代理与最终代理是同一个
(4)方案 4:解耦循环依赖(长期最优)
// 抽取第三方服务
@Component
public class CommonService {
public void doCommon() { /* 原 A 和 B 相互调用的逻辑 */ }
}
@Component
@Transactional
public class A {
@Autowired
private CommonService commonService;
public void doA() { commonService.doCommon(); }
}
@Component
@Transactional
public class B {
@Autowired
private CommonService commonService;
public void doB() { commonService.doCommon(); }
}
4. 避坑总结
- 不要盲目禁用循环依赖(
allow-circular-references=false) - 优先使用
@Lazy方案,无侵入、风险低 - 避免过度依赖
allowRawInjectionDespiteWrapping,长期需解耦 - Java 8 环境推荐
@EnableAspectJAutoProxy(proxyTargetClass=true),CGLIB 代理更稳定 - 排查技巧:通过
BeanPostProcessor打印 Bean 类型,确认是否为代理对象
更多推荐


所有评论(0)