从源码层面深度剖析Spring循环依赖 | 京东云技术团队

这篇具有很好参考价值的文章主要介绍了从源码层面深度剖析Spring循环依赖 | 京东云技术团队。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

以下举例皆针对单例模式讨论

图解参考 https://www.processon.com/view/link/60e3b0ae0e3e74200e2478ce

1、Spring 如何创建Bean?

对于单例Bean来说,在Spring容器整个生命周期内,有且只有一个对象。

Spring 在创建 Bean 过程中,使用到了三级缓存,即 DefaultSingletonBeanRegistry.java 中定义的:

    /** Cache of singleton objects: bean name to bean instance. */
    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 ConcurrentHashMap<>(16);

以 com.gyh.general 包下的 OneBean 为例,debug springboot 启动过程,分析spring是如何创建bean的。

参考图中 spring创建bean 的过程。其中最关键的几步有:

1.getSingleton(beanName, true) 依次从一二三级缓存中查找bean对象,如果缓存中存在对象,则直接返回(early);

2.createBeanInstance(beanName, mbd, args) 选一个合适的构造函数,new实例对象(instance),此时的instance中依赖的属性还都是null,属于半成品;

3.singletonFactories.put(beanName, oneSingletonFactory) 利用上一步的instance,构建一个 singletonFactory,并将其放到三级缓存中;

4.populateBean(beanName, mbd, instanceWrapper) 填充bean:为该bean定义的属性创建对象或赋值;

5.initializeBean("one",oneInstance, mbd) 初始化bean:对bean进行初始化或其他加工,如生成代理对象(proxy);

6.getSingleton(beanName, false) 依次在一二级缓存中查找,检查是否有因循环依赖导致提前生成的对象,有的话与初始化后的对象是否一致;

2、Spring 如何解决循环依赖?

以 com.gyh.circular.threeCache 包下的 OneBean 和 TwoBean 为例 ,两个 Bean 相互依赖(即形成闭环)。

参考图中 spring解决循环依赖 的过程可知,spring利用三级缓中的 objectFactory 生成并返回一个 early 对象,提前暴露这个 early 地址,供其他对象依赖注入使用,以此解决循环依赖问题。

3、Spring 不能解决哪些循环依赖?

3.1 循环中使用了 @Async 注解

3.1.1 为什么循环中使用了 @Async 会报错?

以 com.gyh.circular.async 包下的 OneBean 和 TwoBean 为例,两个bean相互依赖,且oneBean中的方法使用了 @Async 注解,此时启动spring失败,报错信息为:org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'a.one': Bean with name 'a.one' has been injected into other beans [a.two] in its raw version as part of a circular reference, but has eventually been wrapped. This means that said other beans do not use the final version of the bean. This is often the result of over-eager type matching - consider using 'getBeanNamesForType' with the 'allowEagerInit' flag turned off, for example.

并通过debug代码,发现报错位置在 AbstractAutowireCapableBeanFactory#doCreateBean 方法内,由于 earlySingletonReference != null 且 exposedObject != bean,导致报错。

从源码层面深度剖析Spring循环依赖 | 京东云技术团队,硬核干货,spring,java,循环依赖,源码分析,后端

结合流程图中 spring解决循环依赖 及上述图片中可知:

1.行1中 bean 为 createBeanInstance 创建的实例(address1)

2.行2中 exposedObject 为 initializeBean 后生成的代理对象(address2)

3.行3中 earlySingletonReference 为 getEarlyBeanReference 时创建的对象【此处地址同bean(address1)】

深层原因为:先前 TwoBean 在 populateBean 时已经依赖了地址为 address1 的 earlySingletonReference 对象,而此时 OneBean 经过 initializeBean 之后,返回了地址为 address2 的新对象,导致spring不知道哪个才是最终版的bean,所以报错。

earlySingletonReference 是如何生成的,参考getSingleton(“one”, true)过程。

3.1.2 循环中使用了 @Async 一定会报错吗?

依然以 com.gyh.circular.async 包下的 OneBean 和 TwoBean 为例,两个bean相互依赖,使 TwoBean(非OneBean)中的方法使用了 @Async 注解,此时启动spring成功,并未报错。

debug代码可知:虽然TwoBean 使用了 @Async 注解,但其 earlySingletonReference = null; 故不会引起报错。

从源码层面深度剖析Spring循环依赖 | 京东云技术团队,硬核干货,spring,java,循环依赖,源码分析,后端

深层原因为:OneBean 先被创建,TwoBean 后创建,再整条链路中,并未在三级缓存中查找过 TwoBean 的 objectFactory 。(OneBean在创建过程中,被找过两次,即 one-> two ->one;TwoBean 的创建过程中,只找过它一次,即 two ->one。)

由此可得:@Async 造成循环依赖报错的先约条件为:

1.循环依赖中的 Bean 使用了 @Async 注解

2.且这个 Bean,比循环内其他 Bean 先创建。

3.注:一个Bean可能会同时存在于多个循环内;只要存在它是某个循环内第一个被创建的Bean,那么就会报错。

3.1.3 为什么循环中使用了 @Transactional 不会报错?

已知使用了 @Transactional 注解的 Bean,Spring 也会为其生成代理对象,但为什么这种 Bean 在循环里时不会产生报错呢?

以 com.gyh.circular.transactional 包下的 OneBean 和 TwoBean 为例,两个 Bean 相互依赖,且 OneBean 中的方法使用了 @Transactional 注解,启动Spring成功,并不会报错。

debug 代码可知,生成 OneBean 过程中,虽然 earlySingletonReference != null,但 initializeBean 之后的 exposedObject 和 原始实例的地址相同(即 initializeBean 步骤中,并未对实例生成代理),所以不会产生报错。

从源码层面深度剖析Spring循环依赖 | 京东云技术团队,硬核干货,spring,java,循环依赖,源码分析,后端

3.1.4 为什么同样是代理会产生两种不同的现象?

同样是生成代理对象,同样是参与到循环依赖中,会产生不同现象的原因是:当他们处在循环依赖中时,生成代理的节点不同:

1.@Transactional 在 getEarlyBeanReference 时生成代理,提前暴露出代理之后的地址(即最终地址);

2.@Async 在 initializeBean 时生成代理,导致提前暴露出去的地址不是最终地址,造成报错。

为什么 @Async 不能在 getEarlyBeanReference 时生成代理呢?对比下两者执行的代码过程发现:

两者都是在 AbstractAutoProxyCreator#getEarlyBeanReference 的方法对原始实例对象进行包装,如下图

从源码层面深度剖析Spring循环依赖 | 京东云技术团队,硬核干货,spring,java,循环依赖,源码分析,后端

使用 @Transactional 的Bean 在 create proxy 时,获取到一个advice ,随即生成了代理对象 proxy.

从源码层面深度剖析Spring循环依赖 | 京东云技术团队,硬核干货,spring,java,循环依赖,源码分析,后端

而使用 @Async 的Bean 在 create proxy 时,没有获取到 advice,不能被代理.

从源码层面深度剖析Spring循环依赖 | 京东云技术团队,硬核干货,spring,java,循环依赖,源码分析,后端

3.1.5 为什么@Async 在 getEarlyBeanReference 时不能返回一个 advice?

在 AbstractAutoProxyCreator#getAdvicesAndAdvisorsForBean 方法内,其主要做的事情有:

1.找到当前 spring 容器中所有的 Advisor

2.返回适配当前 bean 的所有 Advisor

第一步返回的 Advisor 有 BeanFactoryCacheOperationSourceAdvisor 和 BeanFactoryTransactionAttributeSourceAdvisor,并无处理 Async 相关的 Advisor.

刨根问底,追查为什么第一步不会返回处理 Async 相关的 Advisor?

已知使用 @Async @Transactional @Cacheable 需要提前进行开启,即提前标注 @EnableAsync、@EnableTransactionManagement、@EnableCaching 。

以 @EnableTransactionManagement、@EnableCaching 为例,在其注解定义中,引入了Selector类,Selector中又引入了Configuration 类,在 Configuration 类中,创建了对应 Advisor 并放到了 spring容器中,所以第一步才能得到这两个 Advisor.

而 @EnableAsync的定义中引入的 Configuration 类,创建的是 AsyncAnnotationBeanPostProcessor 并非一个 Advisor,所以第一步不会得到它,所以 @Async 的 bean 不会在这一步被代理。

3.2 构造函数引起的循环依赖

以 com.gyh.circular.constructor 包下的 OneBean 和 TwoBean 为例,两个类的构造函数中各自依赖对方,启动spring,报错:org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'c.one': Requested bean is currently in creation: Is there an unresolvable circular reference?

debug 代码可知,两个bean在根据构造函数 new instance 时,就已经陷入的死循环,无法提前暴露可用的地址,所以只能报错。

4、如何解决以上循环依赖报错?

1.不用 @Async,将需要异步操作的方法,放到线程池中执行。(推荐)

2.提出 @Async 标注的方法。(推荐)

3.将使用 @Async 的方法提出到单独的类中,该类只做异步处理,不做其他业务依赖,即避免形成循环依赖,从而解决报错问题。参考 com.gyh.circular.async.extract 包。

4.尽量不使用构造函数依赖对象。(推荐)

5.破坏循环(不推荐)即不形成闭环,在开发之前,规划好对象依赖,方法调用链,尽量做到不使用循环依赖。(较难,随着迭代开发不断变化,很可能产生循环)

6.破坏创建顺序(不推荐)

7.由于使用 @Async 注解的所在类,比循环依赖内其他类先创建时才会报错,那么想办法使该类不先于其他类先创建,也可解决该问题,如:@DependsOn、 @Lazy

作者:京东科技 郭艳红

来源:京东云开发者社区文章来源地址https://www.toymoban.com/news/detail-635494.html

到了这里,关于从源码层面深度剖析Spring循环依赖 | 京东云技术团队的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Spring源码解析(五):循环依赖

    Spring源码系列文章 Spring源码解析(一):环境搭建 Spring源码解析(二):bean容器的创建、默认后置处理器、扫描包路径bean Spring源码解析(三):bean容器的刷新 Spring源码解析(四):单例bean的创建流程 Spring源码解析(五):循环依赖 Spring源码解析(六):bean工厂后置处理器ConfigurationCla

    2024年02月15日
    浏览(43)
  • 源码剖析Spring依赖注入:今天你还不会,你就输了

    在之前的讲解中,我乐意将源码拿出来并粘贴在文章中,让大家看一下。然而,我最近意识到这样做不仅会占用很多篇幅,而且实际作用很小,因为大部分人不会花太多时间去阅读源码。 因此,从今天开始,我将采取以下几个步骤:首先,我会提前画出一张图来展示本章节要

    2024年02月19日
    浏览(39)
  • Spring源码解析——IOC之循环依赖处理

    循环依赖其实就是循环引用,也就是两个或则两个以上的bean互相持有对方,最终形成闭环。比如A依赖于B,B依赖于C,C又依赖于A。如下图所示: 注意,这里不是函数的循环调用,是对象的相互依赖关系。循环调用其实就是一个死循环,除非有终结条件。 最全面的Java面试网站

    2024年02月08日
    浏览(52)
  • spring 的循环依赖以及spring为什么要用三级缓存解决循环依赖

            bean的生命周期         这里简单过一下 class -无参构造 -普通对象 -依赖注入(对加了autowire等的属性赋值) -初始化前-初始化 -初始化后(aop) -放入单例池的map(一级缓存) -bean对象 这里提一点单例bean单例bean 其实就是用mapbeanName,Bean对象创建的,多例bean就不

    2024年02月15日
    浏览(57)
  • 【Spring】Spring循环依赖(超重要!!)

    目录 什么是循环依赖问题 循环依赖具体是怎么解决的 具体的解决步骤: 通俗实例:       严谨的循环依赖解决图例 为什么使用的是三级缓存,二级缓存不够用吗?         Spring的循环依赖是指在Bean之间存在相互依赖关系,形成一个闭环的情况。简单来说,Bean A依赖于

    2024年02月11日
    浏览(35)
  • 【Spring】Spring循环依赖的处理

    循环依赖是指两个或多个组件之间相互依赖,形成一个闭环,从而导致这些组件无法正确地被初始化或加载。这种情况可能会在软件开发中引起问题,因为循环依赖会导致初始化顺序混乱,组件之间的关系变得复杂,甚至可能引发死锁或其他不稳定行为。 在编程中,循环依赖

    2024年02月11日
    浏览(39)
  • Spring解决循环依赖问题

    例如,就是A对象依赖了B对象,B对象依赖了A对象。(下面的代码属于 属性的循环依赖 ,也就是初始化阶段的循环依赖,区别与底下 构造器的循环依赖 ) 问题来了: A Bean创建 —— 依赖了 B 属性 ——  触发 B Bean创建 ——  B 依赖了 A 属性 ——  需要 A Bean(但A Bean还在创建

    2024年02月12日
    浏览(44)
  • Spring解决循环依赖

    目录 什么是spring循环依赖 什么情况下循环依赖可以被处理? spring 如何解决循环依赖 创建A这个Bean的流程 答疑 疑问:在给B注入的时候为什么要注入一个代理对象? 初始化的时候是对A对象本身进行初始化,而容器中以及注入到B中的都是代理对象,这样不会有问题吗? 三级

    2024年02月22日
    浏览(48)
  • 【Spring】循环依赖

    循环依赖是什么? 循环依赖的场景有哪一些? 三级缓存分别是什么?三个Map有什么异同? 三级缓存是如何解决循环依赖的? 为什么要使用三级缓存?为什么不可以用二级缓存? 为什么构造器循环依赖、原型Bean循环依赖无法用三级缓存解决? 看过 Spring源码吗?一般我们说

    2024年02月21日
    浏览(37)
  • Spring循环依赖处理

    循环依赖是指两个或多个组件之间相互依赖,形成一个闭环,从而导致这些组件无法正确地被初始化或加载。这种情况可能会在软件开发中引起问题,因为循环依赖会导致初始化顺序混乱,组件之间的关系变得复杂,甚至可能引发死锁或其他不稳定行为。 在编程中,循环依赖

    2024年02月07日
    浏览(37)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包