敞开成长之旅!这是我参加「日新计划 12 月更文挑战」的第16天,点击查看活动详情

本文正在参加「金石计划 . 瓜分6万现金大奖」

作者:郭艳红

以下举例皆针对单例形式评论

图解参阅 www.processon.com/view/link/6…

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处理循环依靠 及上述图片中可知:

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循环依赖



深层原因为: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循环依赖





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

同样是生成署理目标,同样是参加到循环依靠中,会发生不同现象的原因是:当他们处在循环依靠中时,生成署理的节点不同:

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

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

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

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



从源码层面深度剖析Spring循环依赖



运用 @Transactional 的Bean 在 create proxy 时,获取到一个advice ,随即生成了署理目标 proxy.



从源码层面深度剖析Spring循环依赖



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



从源码层面深度剖析Spring循环依赖



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