Spring无法解决循环依赖的五种场景分析

 更新时间:2025年05月26日 10:50:55   作者:雾缘枯  
本文详细分析Spring框架中五类循环依赖问题(构造器注入、原型作用域、@Async、配置类、BeanPostProcessor),提出应急方案如@Lazy、重构设计,并强调通过单一职责、依赖倒置等设计原则避免循环依赖,需要的朋友可以参考下

一、构造器注入引发的循环依赖

1. 问题复现

@Component
public class ServiceA {
    private final ServiceB serviceB;
    
    @Autowired
    public ServiceA(ServiceB serviceB) { // 构造器注入
        this.serviceB = serviceB;
    }
}
 
@Component
public class ServiceB {
    private final ServiceA serviceA;
    
    @Autowired
    public ServiceB(ServiceA serviceA) { // 构造器注入
        this.serviceA = serviceA;
    }
}

报错信息:Requested bean is currently in creation: Is there an unresolvable circular reference?

2. 原理分析

  • 三级缓存失效:构造器注入要求在实例化阶段完成依赖注入,而此时 Bean 尚未放入三级缓存。
  • 生命周期冲突:

3. 解决方案

  • 方案 1:将其中一个 Bean 改为 Setter / 字段注入
  • 方案 2:使用 `@Lazy` 延迟加载
@Autowired
  public ServiceA(@Lazy ServiceB serviceB) { 
      this.serviceB = serviceB;
  }

二、原型(Prototype)作用域的循环依赖

1. 问题复现

@Scope("prototype")
@Component
public class PrototypeA {
    @Autowired private PrototypeB b;
}
 
@Scope("prototype")
@Component
public class PrototypeB {
    @Autowired private PrototypeA a;
}

2. 原理分析

  • 缓存机制不生效:原型 Bean 不会存入三级缓存,每次请求都创建新实例。

  • Spring 官方限制:明确说明不处理原型 Bean 的循环依赖。

3. 解决方案

  • 重构设计:避免原型 Bean 之间的循环依赖

  • 改用单例:评估是否真的需要原型作用域

三、@Async 注解导致的代理冲突

1. 问题复现

@Service
public class AsyncServiceA {
    @Autowired private AsyncServiceB serviceB;
    
    @Async
    public void asyncMethod() { /* ... */ }
}
 
@Service
public class AsyncServiceB {
    @Autowired private AsyncServiceA serviceA;
}

2. 原理分析

  • 代理时序问题@Async 通过后置处理器生成代理,可能破坏三级缓存机制。

  • 典型错误栈

BeanCreationException: Error creating bean with name 'asyncServiceA': 
Bean with name 'asyncServiceA' has been injected into other beans [...] in their raw version as part of a circular reference.

3. 解决方案

  • 方案 1:对异步方法所在类使用接口代理
@Async
public interface AsyncService {
    void asyncMethod();
}
 
@Service
public class AsyncServiceImpl implements AsyncService { /* ... */ }
  • 方案 2:在注入点添加 @Lazy
@Autowired @Lazy private AsyncServiceA serviceA;

四、Configuration 类之间的循环依赖

1. 问题复现

@Configuration
public class ConfigA {
    @Autowired private ConfigB configB;
}
 
@Configuration
public class ConfigB {
    @Autowired private ConfigA configA;
}

2. 原理分析

  • 配置类加载顺序:配置类需要优先初始化,无法通过常规循环依赖解决。

  • Spring 限制@Configuration 类被视为特殊 Bean,其代理机制与普通 Bean 不同。

3. 解决方案

  • 重构配置类:合并相关配置

  • 使用 @DependsOn:明确指定加载顺序

@Configuration
@DependsOn("configB")
public class ConfigA { /* ... */ }

五、自定义 BeanPostProcessor 引发的冲突

1. 问题复现

@Component
public class CustomProcessor implements BeanPostProcessor {
    @Autowired private ServiceX x; // 依赖其他Bean
}

2. 原理分析

  • 处理器加载时序BeanPostProcessor 需要优先初始化,此时普通 Bean 尚未创建。

  • Spring 启动流程

3. 解决方案

  • 避免在 BeanPostProcessor 中注入其他 Bean

  • 使用延迟注入

private ObjectProvider<ServiceX> xProvider;
  
  public Object postProcessBeforeInitialization(Object bean, String beanName) {
      ServiceX x = xProvider.getIfAvailable();
      // ...
  }

六、终极解决方案工具箱 

问题类型应急方案根治方案
构造器循环依赖@Lazy 注解改为 Setter 注入
原型Bean循环依赖重构作用域引入中间类抽象依赖
AOP代理冲突接口代理模式调整切面作用顺序
配置类循环依赖@DependsOn 指定顺序合并配置类
BeanPostProcessor依赖ObjectProvider 延迟获取分离处理器与业务逻辑

结语:跳出循环依赖的思维陷阱

Spring 的循环依赖处理机制体现了框架设计的高度智慧,但作为开发者,最优雅的解决方案往往不是技术手段,而是架构设计。通过以下原则可从根本上避免循环依赖:

  1. 单一职责原则:拆分臃肿的 Bean

  2. 依赖倒置原则:面向接口编程

  3. 层次化设计:Controller -> Service -> Repository 的严格分层

以上就是Spring无法解决循环依赖的五种场景分析的详细内容,更多关于Spring无法解决循环依赖的资料请关注脚本之家其它相关文章!

相关文章

  • java多线程编程之使用Synchronized关键字同步类方法

    java多线程编程之使用Synchronized关键字同步类方法

    JAVA中要想解决“脏数据”的问题,最简单的方法就是使用synchronized关键字来使run方法同步,看下面的代码,只要在void和public之间加上synchronized关键字
    2014-01-01
  • java8 BigDecimal类型的List求和方式

    java8 BigDecimal类型的List求和方式

    这篇文章主要介绍了java8 BigDecimal类型的List求和方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-11-11
  • 详解Java如何实现基于Redis的分布式锁

    详解Java如何实现基于Redis的分布式锁

    在不同进程需要互斥地访问共享资源时,分布式锁是一种非常有用的技术手段。这篇文章运用图文和实例代码介绍了Java如何实现基于Redis的分布式锁,文章介绍的很详细,对Java和Redis刚兴趣的朋友们可以参考借鉴,下面来一起看看。
    2016-08-08
  • Java陷阱之assert关键字详解

    Java陷阱之assert关键字详解

    这篇文章详细介绍了Java陷阱之assert关键字,有需要的朋友可以参考一下
    2013-09-09
  • Spring中Bean的加载与SpringBoot的初始化流程详解

    Spring中Bean的加载与SpringBoot的初始化流程详解

    这篇文章主要介绍了Spring中Bean的加载与SpringBoot的初始化流程详解,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-11-11
  • springboot post接口接受json时,转换为对象时,属性都为null的解决

    springboot post接口接受json时,转换为对象时,属性都为null的解决

    这篇文章主要介绍了springboot post接口接受json时,转换为对象时,属性都为null的解决,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-10-10
  • Java如何基于EasyExcel实现导入数据校验并生成错误信息Excel

    Java如何基于EasyExcel实现导入数据校验并生成错误信息Excel

    这篇文章主要介绍了Java如何基于EasyExcel实现导入数据校验并生成错误信息Excel,为了优化项目中的文件导入功能,考虑构建一个基于EasyExcel的通用Excel导入框架,主要解决导入数据的校验问题,避免业务代码中堆积大量校验逻辑,需要的朋友可以参考下
    2024-09-09
  • SpringBoot内部外部配置文件加载顺序解析

    SpringBoot内部外部配置文件加载顺序解析

    这篇文章主要介绍了SpringBoot内部外部配置文件加载顺序解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-07-07
  • Mybatis一对多与多对一查询处理详解

    Mybatis一对多与多对一查询处理详解

    这篇文章主要给大家介绍了关于Mybatis一对多与多对一查询处理的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-03-03
  • Java设计模式中的外观模式详解

    Java设计模式中的外观模式详解

    外观模式为多个复杂的子系统,提供了一个一致的界面,使得调用端只和这个接口发生调用,而无须关系这个子系统内部的细节。本文将通过示例详细为大家讲解一下外观模式,需要的可以参考一下
    2023-02-02

最新评论