Spring中循环依赖问题的解决机制总结

一、解决机制

1. 什么是循环依赖

循环依赖是指两个或多个Bean之间相互依赖对方,形成一个闭环的依赖关系。最常见的情况是当Bean A依赖Bean B,而Bean B又依赖Bean A时,就形成了循环依赖。在Spring容器初始化过程中,如果不加以特殊处理,这种循环依赖会导致Bean的创建过程陷入死循环,最终导致应用启动失败。

例如以下代码展示了一个典型的循环依赖场景:

@Service
public class A {
    @Autowired
    private B b;
}

@Service
public class B {
    @Autowired
    private A a;
}

2. Spring三级缓存机制概述

Spring框架通过巧妙的"三级缓存"机制解决了循环依赖问题。这三级缓存在Spring源码中是通过三个Map集合实现的:

// 一级缓存:存放完全初始化好的Bean
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

// 二级缓存:存放原始的Bean对象(尚未填充属性),用于解决循环依赖
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);

// 三级缓存:存放Bean工厂对象,用于解决循环依赖
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

这三级缓存的作用分别是:

  • 一级缓存(singletonObjects):用于存储完全初始化好的Bean,即已经完成实例化、属性填充和初始化的Bean,可以直接被其他对象使用。

  • 二级缓存(earlySingletonObjects):用于存储早期曝光的Bean对象,这些Bean已经完成实例化但还未完成属性填充和初始化。当出现循环依赖时,其他Bean可以引用这个未完全初始化的Bean。

  • 三级缓存(singletonFactories):用于存储Bean的工厂对象,主要用于处理需要AOP代理的Bean的循环依赖问题。它存储的是一个Lambda表达式,这个表达式会返回Bean的早期引用,并在需要时将其放入二级缓存。

3. 三级缓存的查找顺序

当Spring需要获取一个单例Bean时,会按照以下顺序查找:

  1. 首先从一级缓存(singletonObjects)中查找,如果找到直接返回
  2. 如果一级缓存没有,再从二级缓存(earlySingletonObjects)中查找
  3. 如果二级缓存也没有,则从三级缓存(singletonFactories)中查找对应的工厂,如果找到则通过工厂获取对象,并将其放入二级缓存,同时从三级缓存中移除

这种层级查找的机制确保了在循环依赖的情况下,Bean能够被正确地创建和注入。

4. Spring解决循环依赖的详细流程

以A、B两个类相互依赖为例,Spring解决循环依赖的流程如下:

  1. 创建Bean A

    • Spring首先创建Bean A的实例(仅完成实例化,未进行属性填充)
    • 将A的创建工厂放入三级缓存singletonFactories中
    • 开始给A填充属性,发现依赖了B
  2. 创建Bean B

    • Spring开始创建B(因为A依赖B)
    • 实例化B,并将B的创建工厂放入三级缓存
    • 开始给B填充属性,发现依赖了A
  3. 处理循环依赖

    • 此时需要注入A,但A正在创建中
    • Spring尝试从一级缓存查找A,未找到
    • 继续从二级缓存查找A,未找到
    • 最后从三级缓存中找到A的工厂对象
    • 通过工厂获取A的早期引用(可能是原始对象,也可能是代理对象)
    • 将A的早期引用放入二级缓存,并从三级缓存中移除A的工厂
    • 将A的早期引用注入到B中
  4. 完成Bean创建

    • B完成属性填充和初始化,放入一级缓存
    • 返回到A的属性填充流程,将B注入到A中
    • A完成属性填充和初始化,放入一级缓存

通过这个流程,Spring成功解决了循环依赖问题,关键在于提前暴露了Bean的早期引用。

5. 为什么需要三级缓存

很多人会疑惑,为什么需要三级缓存?二级缓存不能解决循环依赖问题吗?

实际上,在不考虑AOP的情况下,二级缓存确实可以解决循环依赖问题。但Spring设计三级缓存的主要目的是为了处理AOP代理的情况。

在Spring中,AOP代理是在Bean生命周期的最后阶段(初始化后)创建的。但如果出现循环依赖,就需要提前创建代理对象。三级缓存中的工厂对象可以在需要时(即真正出现循环依赖时)才创建代理对象,而不是对所有Bean都提前创建代理。

这种设计有以下优势:

  1. 延迟代理对象的创建:只有在真正需要时才创建代理对象,避免了不必要的性能开销
  2. 保持Bean生命周期的一致性:尽可能地保持Bean的标准生命周期流程
  3. 灵活处理各种代理场景:适应不同的AOP实现和代理方式

6. 三级缓存的局限性

虽然三级缓存机制能够解决大多数循环依赖问题,但它仍有一些局限性:

  1. 不能解决构造器注入的循环依赖:因为构造器注入发生在实例化阶段,此时Bean还未被放入三级缓存,所以无法解决

  2. 不能解决prototype作用域的循环依赖:三级缓存机制只对单例Bean有效,对于prototype作用域的Bean,Spring不会缓存其实例,因此无法解决其循环依赖

  3. 不能解决多例Bean之间的循环依赖:原因同上,Spring不会缓存非单例Bean

7. 源码分析

Spring解决循环依赖的核心源码主要在DefaultSingletonBeanRegistry类中,关键方法包括:

  1. getSingleton(String beanName):按照一级、二级、三级缓存的顺序查找Bean
  2. addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory):将Bean的工厂对象放入三级缓存
  3. doGetBean(String name, Class<T> requiredType, Object[] args, boolean typeCheckOnly):获取Bean的主要逻辑

AbstractAutowireCapableBeanFactorydoCreateBean方法中,会在Bean实例化后立即将其工厂对象放入三级缓存,为解决循环依赖做准备。

8. 总结

Spring通过三级缓存机制巧妙地解决了循环依赖问题,其核心思想是将Bean的实例化和初始化分离,提前暴露实例化但未完全初始化的对象。三级缓存的设计不仅解决了基本的循环依赖问题,还优雅地处理了AOP代理场景下的循环依赖。

这种机制体现了Spring框架设计的精妙之处,通过缓存分层和提前暴露对象的方式,在不影响Bean正常生命周期的前提下解决了看似棘手的循环依赖问题。

二、实际示例与解析

为了更直观地理解Spring如何通过三级缓存解决循环依赖问题,我们来看一个具体的示例,并详细分析整个过程。

1. 示例代码

首先,我们创建两个相互依赖的Service类:

@Service
public class UserService {
    @Autowired
    private OrderService orderService;
    
    public void findUserOrders(Long userId) {
        System.out.println("查询用户订单");
        orderService.getOrdersByUserId(userId);
    }
}

@Service
public class OrderService {
    @Autowired
    private UserService userService;
    
    public List<Order> getOrdersByUserId(Long userId) {
        System.out.println("获取用户订单");
        return new ArrayList<>();
    }
    
    public void notifyUser(Long orderId) {
        System.out.println("通知用户订单状态");
        userService.findUserOrders(1L); // 调用UserService的方法
    }
}

在这个示例中,UserService依赖OrderService,而OrderService又依赖UserService,形成了典型的循环依赖。

2. 详细解析Spring处理流程

让我们详细分析Spring如何处理这个循环依赖:

2.1 创建UserService

当Spring容器启动时,会按照Bean定义顺序开始创建Bean。假设先创建UserService

1. 实例化UserService对象(仅调用构造函数,此时内部属性尚未赋值)
2. 将UserService实例的创建工厂添加到三级缓存(singletonFactories)中
   singletonFactories.put("userService", () -> getEarlyBeanReference(beanName, mbd, userService实例))
3. 开始填充UserService的属性,发现需要注入OrderService

2.2 创建OrderService

由于UserService依赖OrderService,Spring开始创建OrderService:

1. 实例化OrderService对象(仅调用构造函数)
2. 将OrderService实例的创建工厂添加到三级缓存中
   singletonFactories.put("orderService", () -> getEarlyBeanReference(beanName, mbd, orderService实例))
3. 开始填充OrderService的属性,发现需要注入UserService

2.3 处理循环依赖

此时出现了关键的循环依赖处理步骤:

1. OrderService需要注入UserService,但UserService正在创建中
2. Spring尝试从一级缓存(singletonObjects)查找UserService,未找到
3. 继续从二级缓存(earlySingletonObjects)查找UserService,未找到
4. 最后从三级缓存(singletonFactories)中找到UserService的工厂对象
5. 调用工厂对象的getObject()方法获取UserService的早期引用
   - 如果UserService需要被代理(如有@Transactional注解),此时会创建代理对象
   - 如果不需要代理,则返回原始对象
6. 将获取到的UserService早期引用放入二级缓存,同时从三级缓存中移除
   earlySingletonObjects.put("userService", userService早期引用)
   singletonFactories.remove("userService")
7. 将UserService的早期引用注入到OrderService中

2.4 完成Bean创建

接下来完成两个Bean的创建过程:

1. OrderService完成属性填充(已注入UserService的早期引用)
2. OrderService完成初始化(调用各种初始化方法)
3. 如果OrderService需要被代理,创建代理对象(AOP)
4. 将完全初始化好的OrderService放入一级缓存
   singletonObjects.put("orderService", 完全初始化的orderService)
5. 返回到UserService的属性填充流程,将完全初始化好的OrderService注入到UserService中
6. UserService完成初始化
7. 如果UserService需要被代理且之前没有提前创建代理,创建代理对象
8. 将完全初始化好的UserService放入一级缓存
   singletonObjects.put("userService", 完全初始化的userService)

3. 关键源码执行分析

让我们看一下在这个过程中涉及的关键源码执行流程:

3.1 从getSingleton方法开始

当Spring尝试获取一个Bean时,首先会调用getSingleton方法:

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    // 首先从一级缓存查找
    Object singletonObject = this.singletonObjects.get(beanName);
    
    // 如果一级缓存没有,且该Bean正在创建中
    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        // 从二级缓存查找
        singletonObject = this.earlySingletonObjects.get(beanName);
        
        // 如果二级缓存也没有,且允许早期引用
        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;
}

3.2 添加Bean到三级缓存

在Bean实例化后,Spring会将其添加到三级缓存中:

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 创建早期引用

当需要处理循环依赖时,Spring会通过getEarlyBeanReference方法获取早期引用:

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

4. AOP场景下的循环依赖

特别值得注意的是,当涉及到AOP代理时,循环依赖的处理会更加复杂。例如,如果我们的示例中添加了事务注解:

@Service
@Transactional
public class UserService {
    // ...
}

@Service
@Transactional
public class OrderService {
    // ...
}

在这种情况下:

  1. 当从三级缓存获取UserService的早期引用时,会通过AbstractAutoProxyCreator.getEarlyBeanReference方法创建代理对象
  2. 这个代理对象会被放入二级缓存,并最终注入到OrderService中
  3. 这就确保了OrderService依赖的是UserService的代理对象,而不是原始对象

这也解释了为什么需要三级缓存而不是二级缓存:三级缓存中存储的工厂可以在需要时(出现循环依赖时)才创建代理对象,而不是对所有Bean都提前创建代理。

5. 总结

通过这个实际示例,我们可以清晰地看到Spring如何通过三级缓存机制解决循环依赖问题:

  1. 实例化Bean后立即将其工厂对象放入三级缓存
  2. 当出现循环依赖时,通过三级缓存获取早期引用(可能是代理对象)
  3. 将早期引用放入二级缓存,并从三级缓存中移除
  4. 使用早期引用完成依赖注入
  5. 最终将完全初始化的Bean放入一级缓存

这种机制既解决了循环依赖问题,又保持了Spring Bean生命周期的完整性,同时还能灵活处理AOP代理场景,体现了Spring框架设计的精妙之处。

<think>嗯,用户想了解Spring框架中循环依赖问题解决方法和工作原理。这个问题Spring开发中很常见,需要仔细解释清楚。 从引用内容来看,用户已经查阅了一些资料,对循环依赖的基本概念有了解。引用[1]和[2]解释了循环依赖的定义和产生原因,引用[3]和[4]则提到了Spring的处理机制和限制。用户现在需要的是更系统性的原理说明和解决方案。 用户可能正在开发一个Spring项目时遇到了循环依赖报错,需要快速解决问题。深层需求应该是既要理解原理避免类似问题,又要找到实际解决方案。作为开发者,用户肯定希望获得可直接应用的方案,同时理解背后的机制。 关于循环依赖的原理,重点要讲清楚三级缓存机制Spring通过提前暴露未初始化的bean实例来解决循环依赖,这个设计很巧妙。需要分层次说明:一级缓存存放完整bean,二级缓存存放早期暴露的bean,三级缓存存放bean工厂。当A依赖B而B又依赖A时,Spring会在创建A的半成品状态时就将其放入缓存,这样B创建时就能获取到这个半成品A。 在解决方案方面,需要区分构造器注入和属性注入的不同处理。构造器注入的循环依赖Spring无法解决,这点必须强调。而属性注入可以通过三级缓存机制解决。还要提到@Lazy注解这种特殊情况下的解决方案。 用户可能还会关心实际开发中的最佳实践。比如优先使用setter注入而非构造器注入,尽量避免循环依赖的设计,以及使用@Lazy延迟加载等技巧。这些实用建议对用户应该很有帮助。 考虑到用户的技术水平,解释时要避免过于理论化。可以用A→B→A这样的循环依赖例子配合缓存机制的分步说明,这样更直观。代码示例选择setter注入的对比也很重要,能直接展示可解决方案的写法。 最后要提醒用户注意prototype作用域的循环依赖问题,这是Spring明确无法处理的场景。很多开发者会忽略这个限制,导致踩坑。</think>Spring Framework 通过**三级缓存机制**和**提前暴露对象**来解决**单例作用域下基于属性注入(Setter/Field)的循环依赖问题**。其核心原理如下: --- ### **Spring 解决循环依赖的原理(单例模式+属性注入)** 1. **三级缓存结构**: * **一级缓存 `singletonObjects`**:存放**完全初始化完成**的单例 Bean。 * **二级缓存 `earlySingletonObjects`**:存放**提前暴露**的 Bean(已实例化但未完成属性填充和初始化)。 * **三级缓存 `singletonFactories`**:存放创建 Bean 的 **ObjectFactory**(工厂对象,用于生成早期引用)。 2. **解决流程(以 A→B→A 为例)**: 1. **创建 A**: * 实例化 A(调用构造器),得到一个**原始对象**。 * 将用于生成 A 早期引用的 **ObjectFactory** 放入**三级缓存**(`singletonFactories`)。 * 开始为 A **填充属性**(依赖注入)。 2. **发现 A 依赖 B**: * 从容器中查找 B。 * B 不存在,**开始创建 B**。 3. **创建 B**: * 实例化 B(调用构造器),得到一个原始对象。 * 将用于生成 B 早期引用的 **ObjectFactory** 放入**三级缓存**。 * 开始为 B **填充属性**(依赖注入)。 4. **发现 B 依赖 A**: * 从容器中查找 A: * **一级缓存**:无(A 未完全初始化)。 * **二级缓存**:无。 * **三级缓存**:**找到 A 的 ObjectFactory**!调用 `getObject()` 方法**获取 A 的早期引用**(此时 A 未完成属性填充)。 * 将获取到的 A 的早期引用**移动到二级缓存**(`earlySingletonObjects`),**并从三级缓存移除**。 * 将 A 的早期引用注入到 B。 * B **完成属性填充**和初始化,成为一个**完整 Bean**。 * 将 B 放入**一级缓存**,**清除二、三级缓存**。 5. **B 创建完成,返回 A 的创建流程**: * 将 B(已完全初始化)注入到 A。 * A **完成属性填充**和初始化,成为一个**完整 Bean**。 * 将 A 放入**一级缓存**,**清除二、三级缓存**(此时二级缓存中 A 的早期引用也被移除)。 3. **关键点**: * **提前暴露引用**:在对象**实例化后、属性填充前**,通过 `ObjectFactory` 将**半成品对象**(仅完成构造,依赖未注入)的引用暴露到**三级缓存**。 * **依赖查找顺序**:查找 Bean 时按 **一级缓存 → 二级缓存 → 三级缓存** 顺序进行。 * **仅适用于单例作用域**:Spring 无法解决 `prototype` 作用域的循环依赖,因为不会缓存原型对象[^4]。 * **仅适用于属性注入(Setter/Field)**:**构造器注入**导致的循环依赖**无法解决**,因为实例化前就需要完整的依赖对象,无法提前暴露引用[^2][^4]。 --- ### **Spring 无法解决循环依赖场景** 1. **构造器注入循环依赖**: ```java @Service public class ServiceA { private final ServiceB serviceB; // 构造器注入 public ServiceA(ServiceB serviceB) { // 创建 A 时必须立即注入 B this.serviceB = serviceB; } } @Service public class ServiceB { private final ServiceA serviceA; // 构造器注入 public ServiceB(ServiceA serviceA) { // 创建 B 时必须立即注入 A this.serviceA = serviceA; } } ``` * **报错**:`BeanCurrentlyInCreationException`[^4]。 * **原因**:实例化 A 需要 B,而实例化 B 又需要 A,形成死锁。 2. **`prototype` 作用域的循环依赖**: * Spring 不为原型 Bean 提供缓存,每次请求都创建新实例,无法提前暴露引用。 --- ### **解决方案与最佳实践** 1. **避免构造器注入循环**: * **改用 Setter/Field 注入**(推荐): ```java @Service public class ServiceA { @Autowired // 或 @Resource private ServiceB serviceB; // Field 注入 } ``` * **重构设计**:解耦相互依赖的逻辑,如引入**接口**或**中间层**(Service Facade)[^3]。 2. **延迟初始化 (`@Lazy`)**: * 对其中一个依赖添加 `@Lazy`,告知 Spring 注入一个**代理对象**,实际使用时再初始化目标 Bean: ```java @Service public class ServiceA { private final ServiceB serviceB; public ServiceA(@Lazy ServiceB serviceB) { // 构造器注入 + @Lazy this.serviceB = serviceB; // 注入的是代理对象 } } ``` 3. **避免不必要的循环依赖**: * **审查设计**:循环依赖常是设计缺陷的信号,考虑使用**事件驱动**、**观察者模式**或**领域服务**重构。 --- ### **总结** | **场景** | Spring 是否可解决 | 关键机制/限制 | |--------------------------|------------------|--------------------------| | **单例 + Setter/Field 注入** | ✅ 是 | 三级缓存、提前暴露半成品 | | **单例 + 构造器注入** | ❌ 否 | 实例化前需完整依赖 | | **原型作用域 (`prototype`)** | ❌ 否 | 无缓存,无法暴露引用 | > 理解三级缓存和提前暴露机制解决 Spring 循环依赖的核心,优先通过**设计优化**和**使用属性注入**避免问题,必要时辅以 `@Lazy` 注解[^1][^2][^4]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值