Spring循环依赖的随手记

Spring中创建Bean的步骤

在Spring中创建Bean大概分三步:

1、实例化,就是new了个对象(不完整的对象)

2、属性注入

3、初始化,执行一些aware接口中的方法,如AOP代理等

如何解决循环依赖

关键步骤是 提前暴露未完全创建完毕的Bean。

在Spring中,只有同时满足一下两点才能解决循环依赖的问题:

1、依赖的Bean必须都是单例。

2、依赖注入的方式,不能全是构造器注入且beanName字母序在前的不能是构造器注入。

为什么必须都是单例

看了源码就会知道,循环依赖的Bean是原型模式的话会直接抛错。

因为如果两个Bean都是原型模式的话,会有一下问题:

创建A1时需要先创建B1,而创建B1时需要创建A2,创建A2时又想要创建B2...

如此反反复复,因为原型模式都是创建新的对象,不是用的以前的对象。

为什么不能全是构造器注入

这里需要结合上面Spring创建Bean的三步来看。

如果全是构造器注入,比如A(B b),那表明在new的时候,就需要得到B,所以就需要先new B,但是B在构造的时候也需要注入A,即B(A a),但是A都没有new好,所以在map中也找不到一个不完整的A,因此如果全是构造器注入的话,这种情况Spring是无法处理循环依赖的。

但是我们可以借用@lazy注解来解决。这里不做过多说明。

前一个set注入,后一个构造器注入可以

这个为什么可以呢?因为我们在实例化A的时候,是可以得到一个不完整的对象A,将其放入map中,后面创建B的时候可以去map里面取出这个不完整的A,整个过程是不会有问题的。

注意:Spring容器是按照字母序创建Bean的,所以A的创建是排在B的前面。

Spring的三级缓存

一级缓存:singletonObjects,存储所有已经创建完毕的单例Bean(完整的Bean)。

二级缓存:earlysingletonObjects,存储所有仅完成实例化,但还未进入属性注入和初始化的Bean。

三级缓存:singletonFactories,存储的是能建立这个Bean的一个工厂,通过工厂能够获取这个Bean,延迟化Bean的生成,工厂生成的Bean会塞入二级缓存。

这三个map是如何配合的

1、首先,使用getBean(beanName)方法时候,先去一级缓存里面查找有没有完整的Bean,如果没有则需要先看对应的bean是否在创建中

2、如果是在创建中则去二级缓存里面去找不完整的bean,如果不是在创建中则返回null,如果还没有找到则去三级缓存里面查找对应的工厂

3、如果找到工厂则通过工厂创建一个不完整的bean,并将其放入二级缓存,然后将三级缓存里面的工厂删除。如果三级缓存也没有找到的话,则返回null。

从上面步骤可以看出,如果在查询发现Bean还没有创建,在第二步就返回null了。这个时候会标记这个Bean正在创建中,实际是调用doCreateBean方法来创建这个Bean

doCreateBean这个方法就会执行上面我们说的三个步骤:

1、实例化

2、属性注入

3、初始化

而在实例化Bean之后,会往三级缓存singletonFactories塞入一个工厂,而调用这个工厂的getObject方法,就能得到一个不完整的Bean,随后将这个bean放入二级缓存,再删除三级缓存里面的工厂。

需要注意,此时Spring是不知道会不会有循环依赖发生的,但是它才不管那么多,始终都会往singletonFactories塞这个工厂,这就是提前暴露

也就是在对象实例化之后,都会在三级缓存里面加入一个工厂,提前暴露不完整的Bean,这样如果被循环依赖了,就可以利用这个工厂得到一个不完整的Bean,也就破坏了循环的条件了

为什么不直接暴露实例化的Bean

上面说了,Spring在实例化对象之后,就会为其创建一个Bean工厂,并将次工厂放入到三级缓存中。因此Spring一开始提前暴露的并不是实例化的Bean,而是将Bean包装起来的ObjectFactory。之所以这么搞是因为涉及到AOP,因为如果要创建的Bean是有代理的话,那么要注入的就应该是代理Bean,而不是原始的Bean。在没有循环依赖的情况下,Spring会在完成属性填充、并且执行完初始化之后再为其创建代理。但是如果出现了循环依赖,要创建的Bean又涉及代理的话,Spring就不得不提前创建代理对象。所以加入三级缓存的作用主要是为了提前获取代理对象。但是Bean如果不涉及代理的话,三级缓存返回的就是原始对象。

d3e1d71eaac44eea9b30b78806b9890c.jpg

 

为什么不直接使用二级缓存来解决循环依赖

在Bean涉及代理的场景下,不管涉不涉及循环依赖,如果Spring选择使用二级缓存来解决循环依赖的话,那么就意味着所有的Bean都需要在实例化完成之后立马为其创建代理,而如果不涉及循环依赖的话,Spring的设计原则是在Bean初始化完成之后才为其创建代理。所以Spring选择了三级缓存,Spring会先在三级缓存放置一个工厂,如果产生循环依赖且涉及的话,那么调用这个工厂就可以提早得到代理对象,如果没有循环依赖且涉及代理,这个工厂根本也就不会被调用,也就会正常走Bean的生命周期(在初始化之后产生代理对象)。

也就是说,在不存在代理的情况下,其实二级缓存也就够了,三级缓存是为了延迟代理的创建,尽量不打破Bean的生命周期。因为没有循环依赖的话,那么就不需要为其提前创建代理,可以将它延迟到初始化完成之后再创建代理对象。

换句话说:

如果创建的Bean有对应的代理,那其他对象注入时,注入的应该是对应的代理对象;但是Spring无法提前知道这个对象是不是有循环依赖的情况,而正常情况下(没有循环依赖情况),Spring都是在创建好完成品Bean之后才创建对应的代理。这时候Spring有两个选择:

不管有没有循环依赖,都提前创建好代理对象,并将代理对象放入缓存,出现循环依赖时,其他对象直接就可以取到代理对象并注入。
不提前创建好代理对象,在出现循环依赖被其他对象注入时,才实时生成代理对象。这样在没有循环依赖`的情况下,Bean就可以按着Spring设计原则的步骤来创建。

Spring选择了第二种方式,那怎么做到提前曝光对象而又不生成代理呢?
Spring就是在对象外面包一层ObjectFactory,提前曝光的是ObjectFactory对象,在被注入时才在ObjectFactory.getObject方式内实时生成代理对象,并将生成好的代理对象放入到第二级缓存Map<String, Object> earlySingletonObjects。
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

2c1ede775435483a98cbb4b1009361dc.jpg

 

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值