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笛卡尔积算法原理与实现方法详解

    Java笛卡尔积算法原理与实现方法详解

    这篇文章主要介绍了Java笛卡尔积算法原理与实现方法,结合实例形式较为详细的分析了笛卡尔积算法的原理及java定义与使用笛卡尔积算法的相关操作技巧,需要的朋友可以参考下
    2017-12-12
  • SpringBoot压缩json并写入Redis的示例代码

    SpringBoot压缩json并写入Redis的示例代码

    由于业务需要,存入redis中的缓存数据过大,占用了10+G的内存,内存作为重要资源,需要优化一下大对象缓存,所以我们需要对json进行压缩,本文给大家介绍了SpringBoot如何压缩Json并写入redis,需要的朋友可以参考下
    2024-08-08
  • 深入探讨Kafka消费者高性能调优与实践

    深入探讨Kafka消费者高性能调优与实践

    这篇文章主要为大家详细介绍了Kafka消费者在高负载环境下的性能优化方案,内容涵盖Kafka基本原理,消费者工作机制,源码解析以及生产环境中的实战案例
    2025-06-06
  • 微信小程序订阅消息推送实战图文教程(Java Spring Boot + Redis)

    微信小程序订阅消息推送实战图文教程(Java Spring Boot + Redis)

    订阅消息是微信小程序提供的一种消息推送方式,用户可以订阅某个公众号或小程序的消息,当有新消息时,系统会自动推送通知给用户,这篇文章主要介绍了微信小程序订阅消息推送(Java Spring Boot+Redis)的相关资料,需要的朋友可以参考下
    2026-04-04
  • Map之computeIfAbsent使用解读

    Map之computeIfAbsent使用解读

    `computeIfAbsent`是Java 8引入的一个Map接口方法,用于简化在Map中获取值的操作,如果指定的键不存在,它会调用提供的函数生成一个新的值,并将其与键关联,这种方法减少了手动检查和插入的样板代码,使代码更加简洁和易读
    2025-02-02
  • Java执行.bat文件的四种高效方法

    Java执行.bat文件的四种高效方法

    在Windows操作系统中,.bat 文件是一种批处理脚本文件,它可以包含一系列命令,用于执行特定的任务,在Java开发中,.bat 文件通常用于简化Java程序的启动和执行过程,本文给大家介绍了Java执行.bat文件的四种高效方法,需要的朋友可以参考下
    2025-08-08
  • 从千千静听歌词服务器获取lrc歌词示例分享

    从千千静听歌词服务器获取lrc歌词示例分享

    这篇文章主要介绍了使用PHP从千千静听歌词服务器获取lrc歌词的方法,大家参考使用吧
    2014-01-01
  • kotlin快速入门之标准函数与静态方法

    kotlin快速入门之标准函数与静态方法

    学完了Kotlin的基础知识,是时候来来学习 Kotlin的标准函数和静态方法了,下面这篇文章主要给大家介绍了关于kotlin快速入门之标准函数与静态方法的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考下
    2021-09-09
  • Java Lock接口实现原理及实例解析

    Java Lock接口实现原理及实例解析

    这篇文章主要介绍了Java Lock接口实现原理及实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-04-04
  • 浅谈Java的两种多线程实现方式

    浅谈Java的两种多线程实现方式

    本篇文章主要介绍了浅谈Java的两种多线程实现方式,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-08-08

最新评论