Spring循环依赖的解决方法

 更新时间:2026年03月11日 09:30:50   作者:程序员码小跳  
Spring循环依赖是指两个或多个Bean相互依赖,形成闭环,Spring通过三级缓存机制默认支持setter注入的循环依赖,但不支持constructor注入的循环依赖(会抛异常),本文介绍Spring循环依赖的解决方法,感兴趣的朋友跟随小编一起看看吧

是什么

Spring循环依赖指两个或多个 Bean 相互依赖,形成闭环。

Spring 通过巧妙的机制(如三级缓存)默认支持 setter 注入的循环依赖,但不支持 constructor 注入的循环依赖(会抛异常)。

两种注入方式

A、构造器注入(如 @Autowired 在 constructor 上):容易造成无法解决的循环依赖,不推荐使用【现象:无法new对象,因为互相是构造函数的入参,编译报错】,会抛 BeanCurrentlyInCreationException。

/**
 * 通过构造器的方式注入依赖,构造器的方式注入依赖的bean,下面两个bean循环依赖
 * 测试后发现,构造器循环依赖是无法解决的
 */
public class ClientConstructor {
    public static void main(String[] args) {
        new ServiceA(new ServiceB(new ServiceA(new ServiceB())));
    }
}

B、Setter 注入:通过 setter 方法或字段注入(如 @Autowired 在字段上),Spring 默认支持

@Component
public class A {
    @Autowired
    private B b;  // setter 注入
}
@Component
public class B {
    @Autowired
    private A a;  // setter 注入
}

循环依赖的解决办法【重点】

重要结论:Spring 内部通过3级缓存来解决循环依赖,核心是“提前暴露”半成品 Bean(AOP 代理前),允许其他 Bean 先引用。所谓的三级缓存其实就是 Spring 容器内部用来解决循环依赖问题的三个 Map,这三个 Map 在 DefaultSingletonBeanRegistry 类中。

三级缓存详解

  1. singletonObjects(一级缓存):我愿称之为单例池,常说的 Spring 容器就是指它(因为默认的bean生命周期是singleton),我们获取单例 bean 就是在这里面获取的,存放已经经历了完整生命周期的Bean对象【存放完全初始化的 Bean】
  2. earlySingletonObjects(二级缓存):存放早期暴露出来的Bean对象,Bean的生命周期未结束【存放已经实例化,但属性还未填充完整的半成品 bean】
  3. singletonFactories(三级缓存):存放可以生成Bean的工厂,用于生产(创建)对象【存放FactoryBean工厂池】

创建过程(以 A 依赖 B,B 依赖 A 为例)

  1. 实例化 A:Spring 调用 getBean(A),创建 A 实例(无属性),放入三级缓存(singletonFactories,作为 lambda:() -> getEarlyBeanReference(A))。
  2. 注入 A 的属性:发现依赖 B,调用 getBean(B)。
  3. 实例化 B:类似,创建 B 实例,放入三级缓存。
  4. 注入 B 的属性:发现依赖 A,从三级缓存获取 A 的工厂,生成早期 A(半成品),放入二级缓存,并注入到 B。
  5. 完成 B:B 初始化完毕,移到一级缓存。
  6. 返回到 A:A 继续注入 B(已完成),完成初始化,移到一级缓存。
  • 如果涉及 AOP:三级缓存的工厂会生成代理对象,确保循环中代理一致。

getSingleton(): 从容器里面获得单例的bean,没有的话则会创建 bean
doCreateBean(): 执行创建 bean 的操作(在 Spring 中以 do 开头的方法都是干实事的方法)
populateBean(): 创建完 bean 之后,对 bean 的属性进行填充
addSingleton(): bean 初始化完成之后,添加到单例容器池中,下次执行 getSingleton() 方法时就能获取到

Bean的创建顺序

Bean的创建顺序是由BeanDefinition的注册顺序来决定的, 当然依赖关系也会影响Bean创建顺序,比如A依赖B,那肯定B先创建,A后创建。

那BeanDefinition的注册顺序由什么来决定的?
主要是由注解(配置)的解析顺序来决定,顺序如下:

 @Configuration 
 @Component 
 @Import-类
 @Bean
 @Import—ImportBeanDefinitionRegistrar

总结

  1. 调用doGetBean()方法,想要获取beanA,于是调用getSingleton()方法从缓存中查找beanA
  2. 在getSingleton()方法中,从一级缓存中查找,没有,返回null
  3. doGetBean()方法中获取到的beanA为null,于是走对应的处理逻辑,调用getSingleton()的重载方法(参数为ObjectFactory的)
  4. 在getSingleton()方法中,先将beanA_name添加到一个集合中,用于标记该bean正在创建中。然后回调匿名内部类的creatBean()方法
  5. 进入AbstractAutowireCapableBeanFactory#doCreateBean(),先反射调用构造器创建出beanA的实例,然后判断。是否为单例、是否允许提前暴露引用(对于单例一般为true)、是否正在创建中〈即是否在第四步的集合中)。判断为true则将beanA添加到【三级缓存】中
  6. 对beanA进行属性填充,此时检测到beanA依赖于beanB,于是开始查找beanB
  7. 调用doGetBean()方法,和上面beanA的过程一样,到缓存中查找beanB,没有则创建,然后给beanB填充属性
  8. 此时beanB依赖于beanA,调用getsingleton()获取beanA,依次从一级、二级、三级缓存中找,此时从三级缓存中获取到beanA的创建工厂,通过创建工厂获取到singletonObject,此时这个singletonObject指向的就是上面在doCreateBean()方法中实例化的bean
  9. 这样beanB就获取到了beanA的依赖,于是beanB顺利完成实例化,并将beanA从三级缓存移动到二级缓存中
  10. 随后beanA继续他的属性填充工作,此时也获取到了beanB,beanA也随之完成了创建,回到getsingleton()方法中继续向下执行,将beanA从二级缓存移动到一级缓存中

到此这篇关于Spring循环依赖详解的文章就介绍到这了,更多相关Spring循环依赖内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 解析springboot集成AOP实现日志输出的方法

    解析springboot集成AOP实现日志输出的方法

    如果这需要在每一个controller层去写的话代码过于重复,于是就使用AOP定义切面 对其接口调用前后进行拦截日志输出。接下来通过本文给大家介绍springboot集成AOP实现日志输出,需要的朋友可以参考下
    2021-11-11
  • Java 创建两个线程模拟对话并交替输出实现解析

    Java 创建两个线程模拟对话并交替输出实现解析

    这篇文章主要介绍了Java 创建两个线程模拟对话并交替输出实现解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-10-10
  • 在Spring Boot中集成RabbitMQ的实战记录

    在Spring Boot中集成RabbitMQ的实战记录

    本文介绍SpringBoot集成RabbitMQ的步骤,涵盖配置连接、消息发送与接收,并对比两种定义Exchange与队列的方式:手动声明(适合复杂路由)和注解绑定(适合快速开发),感兴趣的朋友跟随小编一起看看吧
    2025-06-06
  • Java使用@PathVariable获取路径参数的代码详解

    Java使用@PathVariable获取路径参数的代码详解

    主要用于处理URL中的动态部分,当你的URL有一部分是动态改变的,比如/users/{id},那么你可以使用@PathVariable来匹配这个动态部分,本文通过代码示例给大家介绍了Java使用@PathVariable获取路径参数的方法,需要的朋友可以参考下
    2025-07-07
  • Java实现线程同步方法及原理详解

    Java实现线程同步方法及原理详解

    这篇文章主要介绍了Java实现线程同步方法及原理详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-06-06
  • Spring Boot 集成MyBatis 教程详解

    Spring Boot 集成MyBatis 教程详解

    这篇文章主要介绍了Spring Boot 集成MyBatis 教程详解,非常不错,具有参考借鉴价值,需要的朋友可以参考下
    2017-04-04
  • springboot整合easy-es实现数据的增删改查的示例代码

    springboot整合easy-es实现数据的增删改查的示例代码

    Easy-Es是一款基于ElasticSearch官方提供的RestHighLevelClient打造的低码开发框架,本文主要介绍了springboot整合easy-es实现数据的增删改查的示例代码,具有一定的参考价值,感兴趣的可以了解一下
    2024-03-03
  • JAVA基础之注解与反射的使用方法和场景

    JAVA基础之注解与反射的使用方法和场景

    这篇文章主要给大家介绍了关于JAVA基础之注解与反射的使用方法和场景的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-03-03
  • 基于Spring AOP proxyTargetClass的行为表现总结

    基于Spring AOP proxyTargetClass的行为表现总结

    这篇文章主要介绍了Spring AOP proxyTargetClass的行为表现总结,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-08-08
  • 当Transactional遇上synchronized的解决方法分享

    当Transactional遇上synchronized的解决方法分享

    前些时间刚好刷到了有关于“# 【事务与锁】当Transactional遇上synchronized”这一类的文章,感觉这也是工作中经常会遇到的一类问题了。所以就针对这个话题进行了分析并整理了常用的解决方法,希望对大家有所帮助
    2023-05-05

最新评论