Java 循环依赖、Bean 作用域与 AOP 代理问题全解析(Java 8 环境)

目录

  1. 循环依赖核心逻辑、源码与 Java 8 解决方案
  2. 多例 Bean 定义、声明方式与 Spring 单例 Bean 实现机制
  3. 三级缓存解决循环依赖的实例与源码拆解
  4. 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代理)
  1. 容器创建A:实例化A(A@123,半成品)→ 存入三级缓存(singletonFactories={"a": ObjectFactory(A@123)})→ 填充属性需要B;
  2. 容器创建B:实例化B(B@456,半成品)→ 存入三级缓存(singletonFactories={"a": ObjectFactory(A@123), "b": ObjectFactory(B@456)})→ 填充属性需要A;
  3. 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");
  4. B注入A@123:完成属性填充→执行初始化→存入一级缓存(singletonObjects={"b": B@456});
  5. A获取B:从一级缓存拿到B@456,完成属性填充→执行初始化;
  6. A存入一级缓存:调用addSingleton("a", A@123),将A@123存入一级缓存,同时移除二级缓存中的A(earlySingletonObjects移除"a");
  7. 最终缓存状态:一级缓存{"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)核心流程源码拆解
  1. 标记 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;
}
  1. 实例化后暴露半成品到三级缓存(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;
}
  1. 三级缓存→二级缓存转换(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 大核心机制
  1. 一级缓存:存储唯一实例

    • 核心变量:singletonObjects(ConcurrentHashMap,线程安全)
    • 作用:所有 getBean() 先查该缓存,命中直接返回,确保实例唯一
  2. 创建锁:避免并发创建

    • 双重检查锁 + synchronized 同步锁(synchronized (this.singletonObjects)
    • singletonsCurrentlyInCreation 集合标记正在创建的 Bean,避免重复创建
  3. 创建后存入缓存
    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 等),成为完整 Bean A@123
步骤 8:A 存入一级缓存,移除二、三级缓存
  • 调用 addSingleton("a", A@123) 存入一级缓存
  • 最终缓存状态:
    • 一级:{"a"→A@123, "b"→B@456};二级:空;三级:空;正在创建:空

4. 三级缓存核心作用总结

步骤缓存操作核心目的
2A 半成品存入三级缓存提前暴露 A,让依赖 A 的 B 能获取半成品
4A 半成品从三级→二级缓存避免重复生成半成品,确保早期实例唯一
5B 完整实例存入一级缓存供 A 注入使用,完成 B 的初始化闭环
8A 完整实例存入一级缓存保证单例复用,提供最终可用 Bean

四、AOP 动态代理与循环依赖的冲突问题及解决方案

1. AOP+循环依赖报错场景(Java 8 环境)

(1)报错条件(需同时满足)
  1. 循环依赖双方都需要 AOP 代理(如都加 @Transactional@Async
  2. 依赖注入方式为字段注入或 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)冲突流程
  1. A 实例化后暴露原始对象到三级缓存,A 注入 B 触发 B 创建
  2. B 依赖 A,从三级缓存获取 A 的原始对象,提前生成 A 的代理 A@proxy 并注入 B
  3. B 初始化完成,生成自己的代理 B@proxy 存入一级缓存
  4. A 继续初始化,再次生成 A 的代理 A@proxy2(与 A@proxy 是不同实例)
  5. 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. 避坑总结

  1. 不要盲目禁用循环依赖(allow-circular-references=false
  2. 优先使用 @Lazy 方案,无侵入、风险低
  3. 避免过度依赖 allowRawInjectionDespiteWrapping,长期需解耦
  4. Java 8 环境推荐 @EnableAspectJAutoProxy(proxyTargetClass=true),CGLIB 代理更稳定
  5. 排查技巧:通过 BeanPostProcessor 打印 Bean 类型,确认是否为代理对象
Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐