spring 循环依赖_spring 基础七循环依赖

前言

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

循环依赖是什么?

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

解决循环依赖的前置条件

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

  1. 出现循环依赖的bean 必须是单例,所以原型bean是不支持的.

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

    1
    2
    3
    if (isPrototypeCurrentlyInCreation(beanName)) {  
    throw new BeanCurrentlyInCreationException(beanName);
    }

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

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

Spring文档也有提到该情况:bcfe8e7bd7bb51f2d4fb49096d9a2ab3.png

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

Spring循环依赖解决方式

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

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

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

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

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

1
2
3
4
getBean(String name)  
---> doGetBean(final String name, @Nullable final Class requiredType,
@Nullable final Object[] args, boolean typeCheckOnly)
---> getSingleton(String beanName, boolean allowEarlyReference)

40c3bc7b18f94862eb795e1e63440961.png
看源码第一步先从singletonObjects中拿实例,那singletonObjects是啥?252958a01a7e7503515f2db788701727.png
这三个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,循环依赖创建两个实例过程结束.983148a851aa001add7e5b58480ade85.png
    上图来源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再完成它的整个生命周期。至此,循环依赖结束!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值