spring 基础七-Spring 循环依赖

前言

当我们使用Spring 体系时,应用一个依赖的类只要加上@Autowired或者@Resource 就能直接使用了,但是正常很多情况下应用的类与被应用的存在相互依赖的关系,这样就会出现循环依赖,Spring是怎样解决循环依赖的?

循环依赖是什么?

通常情况下,说Spring循环依赖的问题都默认所有的实例都是单例,所以说Spring原型实例是不支持循环依赖的,其实例的属性出现互相应用的场景.
通常的情况是Bean之间的相互引用如下图:

解决循环依赖的前置条件

Spring解决循环依赖是有前置条件的,有两个必要的条件:

  1. 出现循环依赖的bean 必须是单例,所以原型bean是不支持的.
  2. 依赖注入的方式不能全是构造器注入的方式.
    第一点很好理解,创建A实例时,发现要注入原型字段B,所以去创建B,发现又需要原型A,进入死循环,就会出现
    StackOverflow或者OutOfMemory,所以Spring会为了避免这种情况首先做出判断,如果是原型非单例类直接抛异常(BeanCurrentlyInCreationException).
    if (isPrototypeCurrentlyInCreation(beanName)) {  
        throw new BeanCurrentlyInCreationException(beanName);
    }
    

第二点怎么理解?
第二点说不能全是构造注入,那如果我们使用构造器会出现什么情况?看代码:

实例A通过构造依赖实例B,实例B通过构造依赖实例A,项目启动出现下面异常,spring很贴心还用图的方式告诉我们循环依赖了.

Spring文档也有提到该情况:

所以说Spring解决循环依赖必须满足上面两种前置条件.

Spring循环依赖解决方式

Spring 创建bean的时候默认是按照自然排序来进行创建,所以上面例子中先创建TestA,Spring创建bean的过程是分为三步:

  1. 实例化,对应方法:AbstractAutowireCapableBeanFactory中的createBeanInstance方法(通俗说发就是new了一个对象)

  2. 属性注入,对应方法:AbstractAutowireCapableBeanFactory的populateBean方法(填充第一步对象中的各个属性)

  3. 初始化,对应方法:AbstractAutowireCapableBeanFactory的initializeBean(执行aware接口中的方法,初始化方法,完成AOP代理)

翻开AbstractAutowireCapableBeanFactory 源码发现实例化一个bean方法调用链路为:

getBean(String name)  
---> doGetBean(final String name, @Nullable final Class<T> requiredType,
			@Nullable final Object[] args, boolean typeCheckOnly) 
            ---> getSingleton(String beanName, boolean allowEarlyReference)


看源码第一步先从singletonObjects中拿实例,那singletonObjects是啥?

这三个Map非常重要:

  • singletonObjects 俗称"单例池容器”,缓存创建完成单例Bean的地方,就是我们实例化Bean的容器.
  • singletonFactories 映射创建Bean的原始工厂
  • earlySingletonObjects 映射Bean的早期引用,也就是说在这个Map里的Bean不是完整的,甚至还不能称之为"Bean",只是一个Instance.
    singletonFactories 与 earlySingletonObjects 只是两个辅助容器,在创建实例时过渡用,创建完成后就会清除掉.
    翻阅源码查看创建流程:
    循环依赖的创建过程为:通过getSingleton 创建TestA调用doCreateBean此时将创建TestA的工厂类记录在singletonFactories容器中,再调用poplateBean填充TestA属性,发现需要创建B,通过getSingleton 创建B,调用doCreateBean 方法,此时记录TestB的工厂类到singletonFactories容器中,调用poplateBean 填充属性发现需要创建TestA,调用getSingleton方法,发现singletonFactories 中有记录TestA的创建工厂,利用TestA的创建工厂创建TestA,并将TestA保存在earlySingletonObjects中,并将singletonFactories中的TestA工厂移除,然后填充TestB属性TestA,TestB最后调用addSingleton 将B放入singletonObjects,TestB的实例化完成,在填充完TestA的属性TestB,TestA最后调用addSingleton将TestA实例添加到singletonObjects,并移除earlySingletonObjects中的TestA,循环依赖创建两个实例过程结束.

    上图来源Vt,侵删
为啥要用singletonFactories?

这个工厂的目的在于延迟对实例化阶段生成的对象的代理,只有真正发生循环依赖的时候,才去提前生成代理对象,否则只会创建一个工厂并将其放入到singletonFactories中,但是不会去通过这个工厂去真正创建对象,所以说singletonFactories不会提升Spring初始化实例的效率,因为如果没有循环依赖直接可以在earlySingletonObjects中就可以拿到需要的实例.

小结

Spring通过三个Map解决了循环依赖,其中第一个容器为单例池(singletonObjects),第二个容器为早期曝光对象earlySingletonObjects,第三个容器为早期曝光对象工厂(singletonFactories)。当A、B两个类发生循环引用时,在A完成实例化后,就使用实例化后的对象去创建一个对象工厂,并添加到singletonFactories中,如果A被AOP代理,那么通过这个工厂获取到的就是A代理后的对象,如果A没有被AOP代理,那么这个工厂获取到的就是A实例化的对象。当A进行属性注入时,会去创建B,同时B又依赖了A,所以创建B的同时又会去调用getBean(a)来获取需要的依赖,此时的getBean(a)会从singletonObjects中获取,第一步,先获取到singletonFactories中的工厂;第二步,调用对象工工厂的getObject方法来获取到对应的对象,得到这个对象后将其注入到B中。紧接着B会走完它的生命周期流程,包括初始化、后置处理器等。当B创建完后,会将B再注入到A中,此时A再完成它的整个生命周期。至此,循环依赖结束!

参考文档

讲一讲Spring中的循环依赖
图解Spring解决循环依赖
Spring官网

posted @ 2023-02-03 14:42  年年糕  阅读(50)  评论(0编辑  收藏  举报