Spring Boot循环依赖,及失败时的解决方案

本文深入探讨了SpringBoot中Bean的注入与创建流程,特别是针对循环依赖问题的处理机制。通过对比不同注入方式(如构造函数注入、Field注入、Setter注入)的影响,详细解释了循环依赖产生的原因及解决方案。

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

循环依赖
        说循环依赖之前,先简单说一下Spring Boot中Bean注入与创建这两步的流程。假设AB互相依赖,Spring Boot先扫到A,那么AB创建及注入流程是这样的:

创建A对象 —> 查找A依赖—> 发现A依赖B —> (创建B实例 —> 查找B依赖—>发现B依赖A —> 注入A实例 —> B初始化完成)—>  为A注入B实例。

        所以在这个流程中,基于filed的注入或setter方法的注入都不会产生问题;但是当采用构造函数注入时,就有可能产生问题(不是一定,原因在下面解答),这是因为对象在构造函数未执行完之前,为了保证对象的完整性,外部通常是无法获取其引用的,以防止对象逸出(其实这也是单例中为什么要加volita的原因,保证顺序性)。

        在Spring Boot中,会记录这种处于构造过程中的实例的名称,每次创建对象时,先检测该对象是否处于“创建中”。由于Spring Boot启动时通过单线程加载类,所以如果某个要创建的对象处于创建中,则说明存在循环依赖。

相关源码如下:

----DefaultListableBeanFactory.class
    public void preInstantiateSingletons() throws BeansException {
        // Trigger initialization of all non-lazy singleton beans...
        for (String beanName : beanNames) {
            //...
            getBean(beanName);...	
        }
    }  

----AbstractAutowireCapableBeanFactory.class     
    protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException {
        //...
        if (instanceWrapper == null) {
            instanceWrapper = createBeanInstance(beanName, mbd, args); //创建Bean
        }
        //...Initialize the bean instance.
        Object exposedObject = bean;
        try {
            populateBean(beanName, mbd, instanceWrapper);  //注入依赖
            exposedObject = initializeBean(beanName, exposedObject, mbd);
        }
        //...
    } 

循环依赖--例1:下面的orderService与goodsItemService互相依赖,注入方式为field注入。经测试下面这种形式的循环依赖,在Spring Boot中不会产生任何问题,Spring Boot能正常启动。上面的流程能正常走完。

@Service
public class OrderService {
    @Autowired
	private GoodsItemService goodsItemService;
}

@Service
public class GoodsItemService {
	@Autowired
	private  OrderService orderService;
}

循环依赖--例2:将上述orderService中goodsItemService的注入方式改成构造器注入。

@Service
public class OrderService {	
	private GoodsItemService goodsItemService;	
	@Autowired
	private OrderService (GoodsItemService goodsItemService) {
		this.goodsItemService = goodsItemService;
	}
}

  此时启动Spring Boot有可能会出现下面的异常,注意只是有可能并不是一定,这取决于这两Java类在你机器上所处的物理位置,因为Spring Boot是通过扫包找到类进行加载,创建及初始化的。所以如果Spring Boot先创建goodsItemService<B>则不会有这个问题。

***************************
APPLICATION FAILED TO START
***************************

Description:

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

┌─────┐
|  goodsItemService defined in file [D:\project-workspace\springboot-test\target\classes\com\sb\GoodsItemService.class]
↑     ↓
|  orderService (field private com.sb.GoodsItemService com.sb.OrderService.orderService)
└─────┘

循环依赖解决方案

  1)修改注入方式,尽量不要使用基于构造函数的注入。比如Setter、Field、@PostConstruct等注入方式

  2)如果一定要使用构造函数的注入,可以加上@Lazy注解,先注入代理对象,当首次使用时再创建对象完成注入。

 

Spring Boot中,循环依赖是一个常见但棘手的问题。当两个或多个Bean相互依赖,就会形成一个依赖环,导致应用启动失败,并抛出类似 `The dependencies of some of the beans in the application context form a cycle` 的错误。 --- ### 三级缓存机制:Spring解决循环依赖的核心策略 Spring通过**三级缓存机制**来处理部分类型的循环依赖问题。其核心思想是将Bean的创建过程拆分为**实例化**和**初始化**两个阶段,并在适当的候提前暴露一个“早期引用”(Early Reference),供其他Bean引用,从而打破循环。 - **一级缓存**(singletonObjects):存放完全初始化好的Bean。 - **二级缓存**(earlySingletonObjects):存放提前暴露的早期Bean(尚未完成属性填充和初始化)。 - **三级缓存**(singletonFactories):存放用于生成早期Bean的工厂对象。 这种机制适用于**基于setter方法注入**的场景,但在使用**构造器注入**Spring无法提前暴露未完全初始化的Bean,因此会直接抛出循环依赖异常[^2]。 --- ### 实战解决方案汇总 #### 1. 使用 `@Lazy` 注解延迟加载 这是最简单且有效的临解决方案之一。通过在某个依赖项上添加 `@Lazy` 注解,可以延迟该Bean的加载机,从而避免在初始化阶段出现循环依赖。 ```java @Service public class UserService { @Lazy @Autowired private RoleService roleService; } ``` 此方式适用于单边依赖的场景,能够有效绕过Spring Boot 2.6+版本中默认开启的循环依赖检查[^3]。 --- #### 2. 改用Setter注入代替构造器注入 由于Spring的三级缓存机制仅支持Setter注入下的循环依赖处理,建议在存在循环依赖风险的组件中避免使用构造器注入。 ```java @Service public class UserService { private RoleService roleService; @Autowired public void setRoleService(RoleService roleService) { this.roleService = roleService; } } ``` 这种方式虽然牺牲了构造器注入的不可变性和更清晰的依赖关系表达,但能有效规避循环依赖问题[^1]。 --- #### 3. 拆分Bean职责,重构设计 最佳实践是**从架构层面避免循环依赖**。可以通过以下方式: - 将公共逻辑提取到第三个Bean中,作为中介服务。 - 对原有类进行功能解耦,确保每个Bean只负责单一职责。 这不仅解决了当前的依赖问题,还能提升系统的可维护性和可测试性。 --- #### 4. 禁用Spring Boot 2.6+的循环依赖检查(慎用) 从Spring Boot 2.6开始,默认禁止循环依赖。可以通过配置启用兼容模式: ```yaml spring: main: allow-circular-references: true ``` 此方法仅建议在紧急修复生产问题使用,因为它可能会掩盖潜在的设计缺陷。 --- #### 5. 使用 `@Primary` 或 `@Qualifier` 明确依赖优先级 在某些复杂场景中,可能存在多个候选Bean,Spring无法自动判断应该注入哪一个,也可能引发间接的循环依赖问题。通过指定主Bean或限定符可以缓解此类问题。 ```java @Primary @Service public class DefaultRoleService implements RoleService { ... } ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值