Spring循环依赖深度解析:从原理到源码,一次性搞懂

Spring循环依赖深度解析:从原理到源码,一次性搞懂

Spring是如何解决循环依赖的?三级缓存到底干了什么?构造器注入为什么不行?本文从源码层面为你彻底讲透。


一、什么是循环依赖?

循环依赖(Circular Dependency),简单来说就是两个或多个Bean之间互相持有对方的引用。就像两个人互相等待对方先开口说话,谁也迈不出第一步。

java 复制代码
@Component
public class OrderService {
    @Autowired
    private UserService userService;
}

@Component
public class UserService {
    @Autowired
    private OrderService orderService;
}

上面的代码中,OrderService 依赖 UserServiceUserService 又依赖 OrderService,形成了一个闭环。这种情况在实际开发中并不少见,尤其是在复杂业务系统中。

循环依赖的三种类型

类型 说明 Spring是否支持
构造器注入循环依赖 通过构造方法参数互相引用 不支持
属性注入(Setter)循环依赖 通过字段或Setter方法注入 支持(单例模式下)
prototype循环依赖 多例模式下的循环依赖 不支持

注意: Spring默认只在「单例模式 + 属性注入」的情况下支持循环依赖。


二、Spring Bean的生命周期

要理解循环依赖的解决原理,首先必须搞懂Spring Bean的生命周期。一个Bean从无到有,大致经历以下步骤:

css 复制代码
[类扫描] → [BeanDefinition] → [实例化] → [属性注入] → [初始化] → [放入单例池]

具体展开来看:

  1. 扫描 :Spring扫描指定包下的类,发现带有@Component等注解的类
  2. 解析BeanDefinition :将类的信息(类型、作用域、懒加载等)封装为BeanDefinition对象
  3. 注册BeanDefinition :将BeanDefinition存入beanDefinitionMap
  4. 实例化:调用构造方法创建对象(此时属性为null,还不是一个完整的Bean)
  5. 属性注入 :通过@Autowired等方式为属性赋值
  6. 初始化 :调用Aware接口回调、@PostConstructInitializingBean.afterPropertiesSet()、自定义init-method
  7. 放入单例池 :Bean创建完成,存入singletonObjects

关键源码入口:

java 复制代码
// AbstractApplicationContext.java
public void refresh() throws BeansException {
    // ...省略前置步骤...
    
    // 步骤1: 调用BeanFactory后置处理器,完成扫描
    invokeBeanFactoryPostProcessors(beanFactory);
    
    // 步骤2: 注册Bean后置处理器
    registerBeanPostProcessors(beanFactory);
    
    // ...省略中间步骤...
    
    // 步骤3: 实例化所有非懒加载的单例Bean
    finishBeanFactoryInitialization(beanFactory);
}

protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
    // 实例化所有单例Bean
    beanFactory.preInstantiateSingletons();
}

三、三级缓存机制------Spring解决循环依赖的核心

Spring使用了三级缓存来解决单例模式下的属性注入循环依赖问题。这是理解循环依赖的关键所在。

3.1 三级缓存是什么?

Spring在DefaultSingletonBeanRegistry中定义了三个Map:

java 复制代码
// DefaultSingletonBeanRegistry.java

// 一级缓存:单例池,存放完整的Bean
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

// 二级缓存:存放早期的Bean引用(已实例化,未初始化)
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

// 三级缓存:存放Bean工厂(ObjectFactory),用于生成早期Bean引用
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

// 正在创建中的Bean名称集合
private final Set<String> singletonsCurrentlyInCreation = ConcurrentHashMap.newKeySet();

三级缓存的作用

缓存级别 名称 存放内容 作用
一级缓存 singletonObjects 完整的Bean 日常获取Bean的来源
二级缓存 earlySingletonObjects 早期Bean引用 解决循环依赖时临时存放
三级缓存 singletonFactories ObjectFactory 生成早期Bean引用的工厂

3.2 三级缓存是如何协作的?

当创建Bean A时,Spring会按以下顺序工作:

  1. 实例化A:调用构造方法创建A的原始对象
  2. 提前暴露A的工厂 :将A的ObjectFactory放入三级缓存
  3. 属性注入A:发现A依赖B,去获取B
  4. 实例化B:调用构造方法创建B的原始对象
  5. 提前暴露B的工厂 :将B的ObjectFactory放入三级缓存
  6. 属性注入B:发现B依赖A,去获取A
  7. 获取A:从三级缓存中拿到A的工厂,调用工厂方法得到A的早期引用,放入二级缓存,删除三级缓存
  8. B属性注入完成,继续初始化B,B创建完成放入一级缓存
  9. 回到A,A的属性注入完成,继续初始化A,A创建完成放入一级缓存

四、源码追踪:循环依赖的解决过程

让我们通过源码一步步追踪Spring是如何解决OrderServiceUserService循环依赖的。

4.1 获取Bean的入口

java 复制代码
// AbstractBeanFactory.java
protected <T> T doGetBean(String name, @Nullable Class<T> requiredType,
        @Nullable Object[] args, boolean typeCheckOnly) {
    
    // 第一步:从一级缓存中获取
    Object sharedInstance = getSingleton(beanName);
    
    if (sharedInstance != null && args == null) {
        // 缓存中存在,直接返回
        beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, null);
    } else {
        // 缓存中不存在,开始创建Bean
        // ...
        // 标记当前Bean正在创建
        beforeSingletonCreation(beanName);
        
        try {
            singletonObject = singletonFactory.getObject();
        } finally {
            // 移除正在创建标记
            afterSingletonCreation(beanName);
        }
    }
    // ...
}

4.2 创建Bean的核心方法

java 复制代码
// AbstractAutowireCapableBeanFactory.java
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
    
    // 第一步:实例化Bean(调用构造方法)
    BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args);
    Object bean = instanceWrapper.getWrappedInstance();
    
    // 第二步:提前暴露早期引用(关键!)
    boolean earlySingletonExposure = (mbd.isSingleton() 
            && this.allowCircularReferences 
            && isSingletonCurrentlyInCreation(beanName));
    
    if (earlySingletonExposure) {
        // 将ObjectFactory放入三级缓存
        addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
    }
    
    Object exposedObject = bean;
    try {
        // 第三步:属性注入
        populateBean(beanName, mbd, instanceWrapper);
        
        // 第四步:初始化Bean
        exposedObject = initializeBean(beanName, exposedObject, mbd);
    } catch (Throwable ex) {
        // 异常处理...
    }
    
    // 第五步:如果存在循环依赖,检查早期引用
    if (earlySingletonExposure) {
        Object earlySingletonReference = getSingleton(beanName, false);
        if (earlySingletonReference != null) {
            if (exposedObject == bean) {
                exposedObject = earlySingletonReference;
            }
        }
    }
    
    return exposedObject;
}

4.3 从缓存中获取Bean

java 复制代码
// DefaultSingletonBeanRegistry.java
@Nullable
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    
    // 1. 从一级缓存获取(完整的Bean)
    Object singletonObject = this.singletonObjects.get(beanName);
    
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        // 一级缓存没有,且Bean正在创建中
        
        // 2. 从二级缓存获取(早期Bean引用)
        singletonObject = this.earlySingletonObjects.get(beanName);
        
        if (singletonObject == null && allowEarlyReference) {
            // 二级缓存也没有
            
            // 3. 从三级缓存获取(ObjectFactory)
            ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
            
            if (singletonFactory != null) {
                // 调用工厂方法,获取早期Bean引用
                singletonObject = singletonFactory.getObject();
                
                // 放入二级缓存
                this.earlySingletonObjects.put(beanName, singletonObject);
                
                // 从三级缓存中移除
                this.singletonFactories.remove(beanName);
            }
        }
    }
    
    return singletonObject;
}

4.4 提前暴露的工厂方法

java 复制代码
// AbstractAutowireCapableBeanFactory.java
protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
    Object exposedObject = bean;
    
    // 遍历所有SmartInstantiationAwareBeanPostProcessor
    for (SmartInstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().smartInstantiationAwareList) {
        exposedObject = bp.getEarlyBeanReference(exposedObject, beanName);
    }
    
    return exposedObject;
}

这个方法非常重要------它就是为什么Spring要用三级缓存而不是二级缓存的原因。通过ObjectFactory,Spring可以在获取早期引用时对Bean进行AOP代理增强,确保注入的是代理对象而非原始对象。


五、完整的循环依赖解决流程

假设有OrderServiceUserService互相依赖,完整流程如下:

第一步:创建OrderService

csharp 复制代码
1. getBean("orderService")
2. 一级缓存没有 → 开始创建
3. 标记orderService正在创建 → singletonsCurrentlyInCreation.add("orderService")
4. 实例化OrderService → new OrderService()(属性userService为null)
5. 将ObjectFactory放入三级缓存 → singletonFactories.put("orderService", factory)

第二步:属性注入OrderService,发现需要UserService

scss 复制代码
6. populateBean() → 需要注入userService
7. getBean("userService")
8. 一级缓存没有 → 开始创建
9. 标记userService正在创建 → singletonsCurrentlyInCreation.add("userService")
10. 实例化UserService → new UserService()(属性orderService为null)
11. 将ObjectFactory放入三级缓存 → singletonFactories.put("userService", factory)

第三步:属性注入UserService,发现需要OrderService

markdown 复制代码
12. populateBean() → 需要注入orderService
13. getBean("orderService")
14. 一级缓存没有(orderService还没创建完)
15. 二级缓存没有
16. 三级缓存有!调用factory.getObject() → 得到OrderService早期引用
17. 放入二级缓存 → earlySingletonObjects.put("orderService", earlyRef)
18. 从三级缓存删除 → singletonFactories.remove("orderService")
19. 返回OrderService早期引用 → UserService属性注入成功

第四步:UserService创建完成

markdown 复制代码
20. UserService初始化完成
21. 放入一级缓存 → singletonObjects.put("userService", userService)
22. 清除二级/三级缓存中userService的记录

第五步:回到OrderService,继续创建

markdown 复制代码
23. OrderService属性注入成功(userService已有值)
24. OrderService初始化完成
25. 放入一级缓存 → singletonObjects.put("orderService", orderService)
26. 清除二级缓存中orderService的记录

六、为什么需要三级缓存?二级不够吗?

这是面试中最常被问到的问题。

如果只用二级缓存(直接存早期Bean引用):

java 复制代码
// 假设只有两级缓存
addSingletonFactory(beanName, bean);  // 直接存原始对象

问题: 如果OrderService需要被AOP代理(比如加了@Transactional),那么注入给UserService的应该是代理对象,而不是原始对象。但如果二级缓存直接存原始对象,UserService拿到的就不是代理对象。

三级缓存通过ObjectFactory解决这个问题:

java 复制代码
// 实际的三级缓存
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

getEarlyBeanReference方法会遍历所有的SmartInstantiationAwareBeanPostProcessor,如果存在AOP切面,就会返回代理对象;如果没有,就返回原始对象。

简单来说:三级缓存的ObjectFactory是为了处理AOP代理的情况,确保注入的是正确的对象(可能是代理对象)。

补充: 如果没有AOP代理的需求,理论上二级缓存就够了。Spring之所以统一使用三级缓存,是为了兼顾AOP场景。


七、常见问题与解决方案

问题1:构造器注入循环依赖

java 复制代码
@Component
public class OrderService {
    private final UserService userService;
    
    @Autowired
    public OrderService(UserService userService) {
        this.userService = userService;
    }
}

报错信息:

vbnet 复制代码
BeanCurrentlyInCreationException: Error creating bean with name 'orderService': 
Requested bean is currently in creation: Is there an unresolvable circular reference?

原因: 构造器注入发生在实例化阶段,此时对象还未创建,无法提前暴露引用。

解决方案:

java 复制代码
// 方案一:改用@Setter注入或字段注入
@Component
public class OrderService {
    @Autowired
    private UserService userService;
}

// 方案二:使用@Lazy注解(推荐)
@Component
public class OrderService {
    private final UserService userService;
    
    @Autowired
    public OrderService(@Lazy UserService userService) {
        this.userService = userService;
    }
}

// 方案三:重构代码,消除循环依赖(最佳实践)

问题2:@Async导致的循环依赖问题

java 复制代码
@Component
public class OrderService {
    @Autowired
    private UserService userService;
    
    @Async
    public void processOrder() {
        // ...
    }
}

在某些Spring版本中,@Async会引入AOP代理,可能导致循环依赖处理异常。

解决方案: 在启动类上添加@EnableAsync,并确保异步配置正确,或使用@Lazy注解。

问题3:Spring Boot 2.6+默认禁止循环依赖

从Spring Boot 2.6开始,默认禁止循环依赖。

报错信息:

markdown 复制代码
***************************
APPLICATION FAILED TO START
***************************

Description:
The dependencies of some of the beans in the application context form a cycle:

┌─────┐
|  orderService (field private com.example.UserService com.example.OrderService.userService)
↑     ↓
|  userService (field private com.example.OrderService com.example.UserService.orderService)
└─────┘

Action:
Relying upon circular references is discouraged and they are prohibited by default. 
Update your application to remove the dependency cycle between beans.

解决方案:

properties 复制代码
# 方案一:在application.properties中允许循环依赖
spring.main.allow-circular-references=true
java 复制代码
// 方案二:通过代码设置
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(Application.class);
        app.setAllowCircularReferences(true);
        app.run(args);
    }
}

问题4:prototype作用域的循环依赖

Spring不支持prototype作用域的循环依赖。因为prototype的Bean不会被缓存,每次获取都是新创建的。

解决方案: 使用ObjectProviderProvider延迟获取。

java 复制代码
@Component
public class OrderService {
    @Autowired
    private ObjectProvider<UserService> userServiceProvider;
    
    public void doSomething() {
        UserService userService = userServiceProvider.getObject();
    }
}

问题5:如何主动关闭循环依赖

java 复制代码
// 方式一:通过API关闭
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
ac.setAllowCircularReferences(false);
ac.register(AppConfig.class);
ac.refresh();

// 方式二:Spring Boot配置
spring.main.allow-circular-references=false

八、最佳实践

虽然Spring提供了循环依赖的支持,但在实际开发中,循环依赖通常意味着架构设计存在问题

推荐的解决方式(按优先级排序):

  1. 重构代码,消除循环依赖(最佳实践)

    • 提取公共逻辑到第三个Bean中
    • 使用事件驱动(ApplicationEvent)解耦
    • 使用观察者模式
  2. 使用@Lazy注解

    • 延迟加载,避免启动时报错
    • 适合临时方案
  3. 使用ObjectProvider/Provider

    • 延迟获取依赖
    • 更加灵活
  4. 使用@PostConstruct

    • 在初始化阶段手动注入

使用事件驱动解耦示例:

java 复制代码
@Component
public class OrderService {
    @Autowired
    private ApplicationEventPublisher eventPublisher;
    
    public void createOrder() {
        // 创建订单逻辑...
        // 发布事件,而非直接调用UserService
        eventPublisher.publishEvent(new OrderCreatedEvent(this, orderId));
    }
}

@Component
public class UserService {
    @EventListener
    public void onOrderCreated(OrderCreatedEvent event) {
        // 处理订单创建后的用户相关逻辑
    }
}

九、总结

知识点 说明
循环依赖 两个或多个Bean互相持有对方引用
前提条件 单例模式 + 属性注入(非构造器)
核心机制 三级缓存
一级缓存 singletonObjects,存放完整Bean
二级缓存 earlySingletonObjects,存放早期Bean引用
三级缓存 singletonFactories,存放ObjectFactory
为什么用三级 为了支持AOP代理场景
Spring Boot 2.6+ 默认禁止循环依赖
最佳实践 重构消除循环依赖 > @Lazy > ObjectProvider

理解Spring的循环依赖解决机制,本质上就是理解Spring的三级缓存设计思想。核心在于提前暴露------在Bean还未完全初始化时,就将其早期引用暴露给其他Bean使用,从而打破循环等待的死锁。

希望这篇文章能帮助你彻底理解Spring循环依赖的原理和解决方案。如果有任何疑问,欢迎在评论区交流讨论!


关注我,持续分享Java技术干货。觉得有帮助的话,点个赞再走吧!

相关推荐
dllxhcjla2 小时前
Spring全套
java·后端·spring
IT 行者2 小时前
Spring AI 2.0.0-M5 发布:全面转向 OpenAI Java SDK
java·人工智能·spring
电商API_180079052473 小时前
淘宝商品评论数据获取指南|批量自动化|api应用
java·爬虫·spring·性能优化·自动化
java1234_小锋3 小时前
Spring AI 2.0 开发Java Agent智能体 - 对话与提示词工程(Prompt)
java·人工智能·spring
小新同学^O^4 小时前
初步了解--> SpringCloud
java·学习·spring·spring cloud
二哈赛车手21 小时前
新人笔记---Spring AI的Advisor以及其底层机制讲解(涉及源码),包含一些遇见的Spring AI的Advisor缺陷问题的解决方案
java·人工智能·spring boot·笔记·spring
薪火铺子21 小时前
Redis 缓存三大问题与解决方案
redis·spring·缓存
pq2171 天前
Spring FactoryBean源码解析
java·spring boot·spring
pq2171 天前
spring如何扫描解析bean(注册bean的多种方式)
spring