关于spring:从源码层面深度剖析Spring循环依赖

57次阅读

共计 5095 个字符,预计需要花费 13 分钟才能阅读完成。

作者:郭艳红

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

图解参考 https://www.processon.com/vie…

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 解决循环依赖 及上述图片中可知:

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; 故不会引起报错。





深层起因为: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 步骤中,并未对实例生成代理),所以不会产生报错。







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

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

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

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

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

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





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





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





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

正文完
 0