spring循环依赖问题

本文深入探讨Spring框架中循环依赖的处理机制,特别是通过构造器和setter注入方式的区别,揭示了循环依赖如何触发BeanCurrentlyInCreationException异常,以及setter注入如何解决这一问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

	spring在DefaultSingletonBeanRegistry类中有以下几种缓存池:
	/** Cache of singleton objects: bean name to bean instance. */
	//singletonObjects:key存bean name,value存bean实例
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

/** Cache of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new HashMap<>(16);

/** Set of registered singletons, containing the bean names in registration order. */
private final Set<String> registeredSingletons = new LinkedHashSet<>(256);

/** Names of beans that are currently in creation. */
//singletonsCurrentlyInCreation存正在创建的bean名称
private final Set<String> singletonsCurrentlyInCreation =
		Collections.newSetFromMap(new ConcurrentHashMap<>(16));

/** Names of beans currently excluded from in creation checks. */
private final Set<String> inCreationCheckExclusions =
		Collections.newSetFromMap(new ConcurrentHashMap<>(16));
类DefaultSingletonBeanRegistry:
/**
	 * Callback before singleton creation.
	 * 在单例bean创建之前尝试回调
	 * <p>The default implementation register the singleton as currently in creation.
	 * 将单例bean注册为正在创建中的默认实现
	 * @param beanName the name of the singleton about to be created
	 * 参数为将要创建的bean名称
	 * @see #isSingletonCurrentlyInCreation
	 */
	protected void beforeSingletonCreation(String beanName) {
		if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
			throw new BeanCurrentlyInCreationException(beanName);
		}
	}

比如当A、B两个service相互依赖的时候:

@Service
public class ServiceAImpl implements ServiceA {
    private ServiceB serviceB;

    public ServiceAImpl(ServiceB serviceB) {
        this.serviceB = serviceB;
    }


    public String print() {
        return "service a print ";
    }
}

@Service
public class ServiceBImpl implements ServiceB {

    private final ServiceA serviceA;

    public ServiceBImpl(ServiceA serviceA) {
        this.serviceA = serviceA;
    }

    public String print() {
        return serviceA.print() + "service b print ";
    }
}

将工程启动,当spring创建bean时候,比如首先创建A:
在这里插入图片描述

可以看到首先将beanname serviceAImpl放到了singletonsCurrentlyInCreation,而由于serviceAImpl依赖了B,这时spring尝试初始化serviceBImpl,又将serviceBImpl放到了singletonsCurrentlyInCreation,又发现serviceBImpl依赖了serviceAImpl,因此又会尝试初始化serviceAImpl;再次来到beforeSingletonCreation方法:
在这里插入图片描述

注意这里Set singletonsCurrentlyInCreation里面已经包括了serviceAImpl和serviceBImpl,再次执行将导致!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)两个条件同时满足,直接导致了抛出异常throw new BeanCurrentlyInCreationException(beanName);

看一下BeanCurrentlyInCreationException
在这里插入图片描述

提示Requested bean is currently in creation: Is there an unresolvable circular reference?
说明spring确实检查到了循环依赖,并且没有办法处理,只能抛出异常

如果将其中一个bean改成使用setter注入方式:


@Service
public class ServiceAImpl implements ServiceA {

    private  ServiceB serviceB;

    public String print() {
        return " service a print ";
    }

    @Autowired
    public void setServiceB(ServiceB serviceB) {
        this.serviceB = serviceB;
    }
}

再次执行:
在这里插入图片描述

首先将A将入到singletonsCurrentlyInCreation,然后往下走,在getSingleton方法中,serviceAImpl添加到了到了earlySingletonObjects中,也就是所谓的提前暴露。

在这里插入图片描述
等到再次回到beforeSingletonCreation时,准备创建B,这时发现singletonsCurrentlyInCreation里面已经空了,而我们在registeredSingletons里面发现了ServiceAImpl的踪影:

在这里插入图片描述

说明当用setter方法注入的时候实际上会用默认无参构造函数将A实例化,而A持有的对象B将延后初始化,等到单例B真正被初始化后,才将B的实例设置到A中。

如果将B改成setter注入也是一样的结果。

结论:
如果两个bean之间有循环依赖,并且都是用构造器方式注入时,由于spring要使用默认构造函数实例化bean,会将两个bean都放到待创建set后然后抛异常。
而只要有至少一个bean初始化方式为setter方式时,就可以使用无参构造函数先将bean初始化,等到所依赖的bean被构造出来后再执行set,也就解决了循环依赖的问题。

<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、付费专栏及课程。

余额充值