前言
在使用spring 开发过程中,我们需要对定义后的bean 通过构造方法,或者bean 注入的方式注入到某个类中进而使用改bean 对应的方法,在此过程中就会出现一个类中注入了n多个bean,几个类中bean 相互注入,出现 a 依赖b,b依赖c,c又依赖a/b 这种循环情况的出现。
提示:以下是本篇文章正文内容,下面案例可供参考
一、spring 循环依赖是什么?
循环依赖是指在spring容器中,存在两个或多个bean之间的互相依赖关系,形成了一个闭环的依赖链。具体来说,当bean a依赖bean b,同时bean b又依赖bean a时,就产生了循环依赖。循环依赖可能会导致以下问题:
死锁:如果循环依赖的解析过程不正确,可能会导致死锁。当容器无法确定如何先实例化哪个bean时,可能会造成死锁情况,导致应用程序无法继续正常执行。
未完全初始化的bean:在解决循环依赖时,spring使用了代理对象来解决依赖问题。这意味着当bean a注入到bean b中时,a可能是一个未完全初始化的代理对象,而不是完全实例化,属性注入,初始化的对象。这可能导致在早期阶段的bean存在一些限制和潜在的问题。
为了解决循环依赖的问题,spring使用了一个两阶段的解析过程:实例化阶段和注入阶段。在实例化阶段,spring创建对象并将其放入缓存中;在注入阶段,spring解决依赖关系并完成注入。
springboot 从 2.6 之前默认开启循环依赖,之后 开始默认不允许出现 bean 循环引用,如果需要则进行手动开启:
spring: main: allow-circular-references:true
二、spring 三级缓存解决单例的循环依赖
spring 三级缓存 实际上使用了3个map 来打破单例对象循环依赖的问题,singletonobjects:这是一级缓存,保存已经实例化且完成了所有的依赖注入和初始化的单例bean实例。earlysingletonobjects:这是二级缓存,保存已经实例化但尚未完成所有的依赖注入和初始化的单例bean实例。
它主要用于解决属性注入时的循环依赖问题。singletonfactories:这是三级缓存,保存创建单例bean实例的objectfactory。
2.1 bean 单例对象生成的过程
对象的生成必须先通过其构造方法进行实例化,然后对其属性赋值,然后执行初始化;以下以 aservice ,bservice,cservice 为例进行研究;
public interface aservice { } public interface bservice { } public interface cservice { } @service public class aserviceimpl implements aservice { @autowired private bservice bservice; } @service public class bserviceimpl implements bservice { @autowired private aservice aservice; } @service public class cserviceimpl implements cservice { @autowired private aservice aservice; @autowired private bservice bservice; }
其中aservice 依赖bservice 的bean ,bservice 依赖aservice 的bean,cservice 依赖aservice ,bservice 的bean ;
思考循环依赖的注入过程:
- 首先 a 实例化,通过a 类的构造方法进行 实例的构建并返回; 对其中的bservice 属性值进行依赖注入;
- 此时需要先从单例池中去获取,bservice 的bean ,bservice 的bean 还没有被创建,所以此时需要先创建bservice 的bean ;
- 调用bservice 的构造方法进行实例的创建,然后对bservice 进行属性注入时需要 对aservice 进行 属性值设置;
然后在从单例池中获取aservice 的bean,发现没有aservice 的bean ,这个时候如果重复在走aservice 的bean 创建过程就会陷入死循环
,显然我们的项目此时是可以成功启动的,也即没有陷入死循环中,那么spring 是怎么解决的?
如果说在创建aservice 的bean时是分为两步:
- 步骤1: 先通过其构造方法完成实例化;
- 步骤2: 对其属性进行填充;
那么如果我们在步骤1 之后 ,就将还没有完成初始化的aservice 的bean 放入到某个地方,然后在初始化其他bean 的时候 如果发现依赖了aservice 的bean 此时可以直接注入aservice 的bean完成对其的引用,即使aservice 的bean还没有进行完整的初始化,我们对aservice 的bean 进行了提前暴露;
这样在aservice 的bean真正完成初始化之后,对aservice 的bean引用也随即完成;这样就打破了bean 的循环依赖,bean 可以正常初始化了;
现在bservice,cservice 中都依赖了aservice 的bean ,显然无法对aservice 的单例bean 实例化两次 ,那么就需要有个地方来存放aservice 的这个还没有完全初始化的bean,这样后续其它的bean 在注入aservice 的bean 时 会发现 aservice 的bean 已经有了,所以就可以直接使用,不需要在额外创建,这里spring 使用 map (二级缓存)来存放已经实例化,但是还没有完全初始化的 bean , 以便于在发生循环依赖时,如果从单例池中获取不到对应的bean 就到二级缓存中在获取一次,如果获取到了可以直接使用,如果获取不到则需要去生成这个bean 并将其放入到二级缓存中;
因为bservice 中已经将aservice的bean 放入到了二级缓存中,所以cservice 可以直接从二级缓存中获取到service 的单例bean ;
到此看起来spring 已经通过二级缓存来提前暴露未初始化完成的bean 而解决了循环依赖,那么为什么还有三级缓存的概念?
在原码中可以看到三级缓存也是一个map ,并且其value 存的是一个对象的工厂
private final map<string, objectfactory<?>> singletonfactories = new hashmap(16);
我们可能已经听说过三级缓存放入的是lambda表达式 的匿名函数,这个函数会在使用到的时候被调用,那么spring 为什么选择放一个匿名函数而不是直接放入一个bean 呢;显然如果直接放入一个bean 那么三级缓存的作用就和二级缓存相同了;所以spring 这样做肯定是有一些原因的,先来看下在原码中三级缓存放入的lambda是什么:
protected object getearlybeanreference(string beanname, rootbeandefinition mbd, object bean) { object exposedobject = bean; smartinstantiationawarebeanpostprocessor bp; if (!mbd.issynthetic() && this.hasinstantiationawarebeanpostprocessors()) { for(iterator var5 = this.getbeanpostprocessorcache().smartinstantiationaware.iterator(); var5.hasnext(); exposedobject = bp.getearlybeanreference(exposedobject, beanname)) { bp = (smartinstantiationawarebeanpostprocessor)var5.next(); } } return exposedobject; }
从以上代码可以执行先把初始的 bean 对象赋给了 exposedobject ,然后如果发现这个bean 是否满足mbd.issynthetic() && this.hasinstantiationawarebeanpostprocessors()
条件,进而判断当前的bean是否是一个合成的代理对象。
在aop中,合成代理对象是通过特定的机制(如jdk动态代理或cglib动态代理)创建的。这个条件可以用来检查当前创建的bean是否是这种合成的代理对象;
如果需要合成代理对象则 进入exposedobject = bp.getearlybeanreference(exposedobject, beanname) 进行代理对象的创建:
abstractautoproxycreator.getearlybeanreference
// 省略代码 public object getearlybeanreference(object bean, string beanname) { object cachekey = this.getcachekey(bean.getclass(), beanname); this.earlyproxyreferences.put(cachekey, bean); return this.wrapifnecessary(bean, beanname, cachekey); } object exposedobject = bean; try { // 递归填充改bean 的其他属性 this.populatebean(beanname, mbd, instancewrapper); exposedobject = this.initializebean(beanname, exposedobject, mbd); } catch (throwable var18) { if (var18 instanceof beancreationexception && beanname.equals(((beancreationexception)var18).getbeanname())) { throw (beancreationexception)var18; } throw new beancreationexception(mbd.getresourcedescription(), beanname, "initialization of bean failed", var18); } // 省略代码 protected object wrapifnecessary(object bean, string beanname, object cachekey) { if (stringutils.haslength(beanname) && this.targetsourcedbeans.contains(beanname)) { return bean; } else if (boolean.false.equals(this.advisedbeans.get(cachekey))) { return bean; } else if (!this.isinfrastructureclass(bean.getclass()) && !this.shouldskip(bean.getclass(), beanname)) { object[] specificinterceptors = this.getadvicesandadvisorsforbean(bean.getclass(), beanname, (targetsource)null); if (specificinterceptors != do_not_proxy) { this.advisedbeans.put(cachekey, boolean.true); object proxy = this.createproxy(bean.getclass(), beanname, specificinterceptors, new singletontargetsource(bean)); this.proxytypes.put(cachekey, proxy.getclass()); return proxy; } else { this.advisedbeans.put(cachekey, boolean.false); return bean; } } else { this.advisedbeans.put(cachekey, boolean.false); return bean; } }
从上面代码可以看到 如果这个对象在缓存中没有而且是必须要进行依赖注入的, 会对实例化化后的改对象,使用this.createproxy 为其生产代理对象并进行返回;所以使用了这个lambda表达式目的就是返回一个普通对象的bean 或者代理对象的bean,那么为什么不直接在bean 实例化之后,就直接调用getearlybeanreference 方法,这样将生成的普通对象或者代理对象的bean 直接放入到二级缓存中,这样岂不是更为直接,显然这样做也是可以解决spring 的循环依赖的问题,而且在二级缓存中存放的对象就是普通对象或者代理生成的对象。
虽然可以这样做但是违反了spring 对代理对象生成的原则,spring 的设计原则是尽可能保证普通对象创建完成之后,再生成其 aop 代理(尽可能延迟代理对象的生成),因为这样做的话,所有代理都提前到了实例化之后,初始化阶段前,显然与尽可能延迟代理对象的生成 原则是违背的。所以在此使用 lambda表达式 ,在真正需要创建对象bean 的提前引用时,才通过 lambda表达式 来进行创建 ,来遵循尽可能延迟代理对象的生成 原则。
没有依赖,有aop 这种情况中,我们知道 aop 代理对象的生成是在成品对象创建完成之后创建的,这也是 spring 的设计原则,代理对象尽量推迟创建,循环依赖 + aop 这种情况中, 代理对象的生成提前了,因为必须要保证其 aop 功能,那么在bean 初始化完成之后,又到了要对改对象进行代理增强的环节,此时spring 又是怎么判断改bean 已经被增强为代理对象,而不需要重新创建代理对象?
public object postprocessafterinitialization(@nullable object bean, string beanname) { if (bean != null) { object cachekey = this.getcachekey(bean.getclass(), beanname); if (this.earlyproxyreferences.remove(cachekey) != bean) { return this.wrapifnecessary(bean, beanname, cachekey); } } return bean; } public object getearlybeanreference(object bean, string beanname) { object cachekey = this.getcachekey(bean.getclass(), beanname); this.earlyproxyreferences.put(cachekey, bean); return this.wrapifnecessary(bean, beanname, cachekey); }
从以上源码中可以看到 在postprocessafterinitialization bean 被初始化完成之后执行的方法 从this.getcachekey 获取到的cachekey ,最后比较两个bean 是否是一个,如果是则说明bean 已经进行过代理,否则则重新执行wrapifnecessary 生成代理对象;
2.2 三级缓存工作过程
既然在spring 容器中bean 是单例的,那么就不可能存在改bean 的多个对象,也即对bean 的所有引用都指向同一个对象;此时就有一个问题,当一个bean 被依赖注入时,怎么知道这个单例的bean 是否已经被初始化?
所以就需要将已经完成初始化的bean 放入到一个地方中,这个地方要满足如果这个bean 已经被初始化过了,则不需要进行在进行初始化,而且存和取都比较方便,spring 选用map 来对其进行存储,其中key 为bean的那么,value 为单列bean:
private final map<string, object> singletonobjects = new concurrenthashmap(256);
当spring创建一个单例bean时,会先检查一级缓存(singletonobjects)
,如果在其中找到bean实例,则直接返回。如果没有找到,则继续执行bean的创建流程,对改bean 进行实例化,如果改bean允许早期暴露则将获取改对象的一个方法即将工厂方法存储在三级缓存(singletonfactories)中
。
在创建bean 如aservice的属性注入过程中,如果发现还依赖了其它的bean,如:bservice 则进入到bservice 的创建过程:首先对bservice 进行实例化(如果bservice也运行早期暴露,则也会被加入到第三级缓存中)、属性注入、初始化;
如果此时bservice 中依赖了aservice 的bean,出现循环依赖问题,先从一级缓存中获取aservice 的bean,此时aservice的bean 是正在被创建的状态,则从二级缓存找 spring会先尝试从二级缓存(earlysingletonobjects)
中获取bean实例的早期引用,以解决循环依赖,如果二级缓存中aservice 的早起引用不存在;则到三级缓存中,执行aservice bean 的lamma 表达式,获取到aservice 的普通对象/代理对象,并将其放入到第二级缓存,同时移除aservice 在第三级的缓存。
一旦bean创建完成,如:bservice 完成了依赖注入,初始化,aop(如果需要被代理)则会将其放入一级缓存(singletonobjects)
,并从二级缓存(earlysingletonobjects)
和三级缓存(singletonfactories)
中移除,即将bservice 放入到一级缓存(单例池中),同时从二级和三级缓存中移除bservice的早期应用和 获取早期引用的lamma 表达式。然后,在aservice 的属性依赖注入中,就可以从一级缓存中获取到bservice 的单例对象,进行属性注入,然后初始化,和aop(如果需要被aop代理),最后从二级和三级缓存中移除aservice的早期应用和 获取早期引用的lamma 表达式。
abstractbeanfactory,dogetbean
获取对应的bean,此处只列举关键代码
protected <t> t dogetbean(string name, @nullable class<t> requiredtype, @nullable object[] args, boolean typecheckonly) throws beansexception { /** ** 省略代码 **/ // 会先检查一级缓存(singletonobjects),如果在其中找到bean实例,则直接返回, // 此方法调用defaultsingletonbeanregistry 下getsingleton 方法 // 方法(1) 从三级缓存中获取对象 object sharedinstance = this.getsingleton(beanname); // 省略 sharedinstance 不为null 直接返回的代码 /** ** 省略代码 **/ // 如果没有找到,则继续执行bean的创建流程,并将工厂方法存储在三级缓存(singletonfactories)中 if (mbd.issingleton()) { // this.getsingleton 通过改bean 的工厂方法创建出来bean 并放入到单例池中 // 方法2:getsingleton(string beanname, objectfactory<?> singletonfactory) 创建对象 sharedinstance = this.getsingleton(beanname, () -> { try { // 创建普通对象/代理对象 并将工厂方法存储在三级缓存(singletonfactories)中 // 此方法调用 abstractautowirecapablebeanfactory 下 createbean 方法然后在调用 docreatebean 方法 return this.createbean(beanname, mbd, args); } catch (beansexception var5) { this.destroysingleton(beanname); throw var5; } }); beaninstance = this.getobjectforbeaninstance(sharedinstance, name, beanname, mbd); } }
方法(1) 从三级缓存中获取对象 this.getsingleton(beanname) 方法:
object sharedinstance = this.getsingleton(beanname)
方法,defaultsingletonbeanregistry
下getsingleton
, 会先检查一级缓存(singletonobjects),如果在其中找到bean实例,则直接返回:
@nullable public object getsingleton(string beanname) { return this.getsingleton(beanname, true); } @nullable protected object getsingleton(string beanname, boolean allowearlyreference) { // 从一级缓存获取完整的bean object singletonobject = this.singletonobjects.get(beanname); // 没有获取到 并且 这个bean 正在初始化中 if (singletonobject == null && this.issingletoncurrentlyincreation(beanname)) { // 从二级缓存获取这个bean 的早期引用 singletonobject = this.earlysingletonobjects.get(beanname); // 没有早期引用 并且运行循环依赖 if (singletonobject == null && allowearlyreference) { synchronized(this.singletonobjects) { // 加对象锁 singletonobject = this.singletonobjects.get(beanname); if (singletonobject == null) { singletonobject = this.earlysingletonobjects.get(beanname); if (singletonobject == null) { // 从三级缓存 获取bena 的 工厂类 objectfactory<?> singletonfactory = (objectfactory)this.singletonfactories.get(beanname); if (singletonfactory != null) { singletonobject = singletonfactory.getobject(); // 二级缓存中放入 this.earlysingletonobjects.put(beanname, singletonobject); // 三级缓存中移除 this.singletonfactories.remove(beanname); } } } } } } return singletonobject; }
单例池中没有改bean 则进入 sharedinstance = this.getsingleton(beanname, () -> {}) return this.createbean(beanname, mbd, args) 方法
通过abstractautowirecapablebeanfactory.docreatebean
创建普通对象/代理对象 并将工厂方法存储在三级缓存(singletonfactories)中:
boolean earlysingletonexposure = mbd.issingleton() && this.allowcircularreferences && this.issingletoncurrentlyincreation(beanname); if (earlysingletonexposure) { if (this.logger.istraceenabled()) { this.logger.trace("eagerly caching bean '" + beanname + "' to allow for resolving potential circular references"); } // addsingletonfactory 放入到三级缓存 this.addsingletonfactory(beanname, () -> { // 获取提前暴露出来的bean 对象 return this.getearlybeanreference(beanname, mbd, bean); }); } try { // 继续对改bean 中的属性进行初始化 this.populatebean(beanname, mbd, instancewrapper); exposedobject = this.initializebean(beanname, exposedobject, mbd); } catch (throwable var18) { if (var18 instanceof beancreationexception && beanname.equals(((beancreationexception)var18).getbeanname())) { throw (beancreationexception)var18; } throw new beancreationexception(mbd.getresourcedescription(), beanname, "initialization of bean failed", var18); }
defaultsingletonbeanregistry.addsingletonfactory: bean
的工厂放入到三级缓存:
protected void addsingletonfactory(string beanname, objectfactory<?> singletonfactory) { assert.notnull(singletonfactory, "singleton factory must not be null"); synchronized(this.singletonobjects) { if (!this.singletonobjects.containskey(beanname)) { // 放入3级缓存map this.singletonfactories.put(beanname, singletonfactory); this.earlysingletonobjects.remove(beanname); // 将改bean 的名称放入到正在创建的bean 的set 集合 this.registeredsingletons.add(beanname); } } }
方法2:getsingleton(string beanname, objectfactory<?> singletonfactory) 创建对象:
此时的singletonfactory 为 this.getsingleton(beanname, () -> { try { // 创建普通对象/代理对象 并将工厂方法存储在三级缓存(singletonfactories)中 // 此方法调用 abstractautowirecapablebeanfactory 下 createbean 方法然后在调用 docreatebean 方法 return this.createbean(beanname, mbd, args); } catch (beansexception var5) { this.destroysingleton(beanname); throw var5; } })
方法中的lamma 表达式,通过 singletonfactory.getobject() 就可以调用到 this.createbean(beanname, mbd, args) 方法
public object getsingleton(string beanname, objectfactory<?> singletonfactory) { assert.notnull(beanname, "bean name must not be null"); synchronized(this.singletonobjects) { // 获取对象锁,然后从单例池中获取bean object singletonobject = this.singletonobjects.get(beanname); if (singletonobject == null) { // 省略代码 try { // 获取bean 的工厂 singletonobject = singletonfactory.getobject(); newsingleton = true; } // 省略代码 if (newsingleton) { // bean 完成后放入到单例翅中 this.addsingleton(beanname, singletonobject); } } return singletonobject; } } protected void addsingleton(string beanname, object singletonobject) { synchronized(this.singletonobjects) { // 单例池中放入改bean this.singletonobjects.put(beanname, singletonobject); // 从三级缓存和二级缓存中移除 this.singletonfactories.remove(beanname); this.earlysingletonobjects.remove(beanname); this.registeredsingletons.add(beanname); } }
案例过程:
在a 依赖于b,b 依赖于c,c又依赖a 的场景中,当实例化 a后对a 的bean 对象进行属性依赖注入时;发现其依赖了b;此时实例化 b,然后对b的bean 对象进行属性依赖注入;发现依赖了c 的bean, 实例化 c,对c 的bean 进行属性注入时,发现依赖了a的bean :
此时在对c的bean依赖注入为:先从单例池(一级缓存获取a的bean),因为a的bean 还在被创建过程中,所以从单例池中的到的是null,在从二级缓存(早期暴露的对象)获取a的早期暴露bean,如果二级缓存也没有,则从三级缓存中执行lamma 表达式,获取到a的早期暴露bean (将a的bean 放入到二级缓存中,并同时从三级缓存中移除a的bean 的lamma 表达式)进行属性依赖注入;然后完成c的依赖注入,初始化 将其放入到单例池中,然后完成b的属性依赖注入,初始化,将其放入到单例池;完成a 的属性依赖注入,初始化,并放入到单例池中;
过程步骤:
- 开始初始化 a,创建 a 的实例并完成属性注入;
- 当a属性注入 过程中发现 a 依赖于 b,spring 会尝试从单例池中获取b的ean,如果获取不到则进入到创建 b 的实例过程;
- 创建 b 的实例,对b 的bean进行依赖注入,发现 b 还有其他依赖,如: c。此时 spring 会先从单例池中获取c的bean,如果获取不到则进入到c的bean 创建过程;;
- 在c实例化后,对c属性依赖注入 过程中,发现 c 依赖于 a,从单例池中获取a 的bean,此时 a 实例还未创建完成,从二级缓存获取a bean 的早期引用;如果还没有,则从三级缓存找到a bean的lamma 表达式,获取到abean 的早期暴露对象,并将abean的早期暴露对象,放入到二级缓存同时,将abean从三级缓存中移除;
- 继续完成对 c 属性注入(如果有),然后进行初始化,最终将c的bean 放入到单例池中,同时删除其在二级和三级缓存的对象;
- 然后完成对b 其它属性依赖注入中,发现 b 不再依赖其他对象,完成 b 的初始化,最终将b的bean 放入到单例池中,同时删除其在二级和三级缓存的对象;
- 继续 a的依赖注入,在 a的依赖注入 过程中,发现 a 依赖于 b,b 的实例早已创建完成,因此可以直接从一级缓存获取 b 的实例;
- 完成 a 的依赖注入,初始化 ,最终将a的bean 放入到单例池中,同时删除其在二级和三级缓存的对象
spring 使用了缓存机制,确保在递归调用时能够正确地获取到已经创建的对象实例,避免死循环。同时,spring 也会处理代理对象的生成和使用,以确保 a、b、c 的代理对象在正确的时间被创建和使用。
三、spring 三级缓存无法解决的单例循环依赖情况
3.1 通过构造方法注入的bean ,出现循环依赖会报错
在a类中通过构造方法的方式注入b的bean,在b类中通过构造方法的方式注入a的bean;在此场景中,
- 在调用a的构造方法进行实例化时,发现依赖的b的bean,需要对b类进行实例化;
- 调用b类的构造方法进行实例化时,发现依赖的a的bean,此时出现循环依赖;
- 然后此时需要对a的bean 提前进行引用的暴露;
- 然而在对a的bean 提前进行引用的暴露,需要用到a 的实例化对象,此时a的实例化对象还没有被创建,则直接报错;
3.2 早期暴露的非aop代理对象引用,出现循环依赖会报错
@service public class aserviceimpl implements aservice { @autowired private bservice bservice; @async public void test(){ system.out.println(bservice); } } @service public class bserviceimpl implements bservice { @autowired private aservice aservice; }
当使用 @async 注解标注一个bean 中的方法为异步方法时,bservice 中注入的aservice aservice 的bean 与最终生成的aservice 的bean 不相同而导致报错;
- 对aservice 进行实例化后,对其bservice 属性进行初始化;
- 对bservice 进行实例化,然后对其aservice 属性进行初始化,此时发现循环依赖;
- 暴露aservice 的bean 到二级缓存中,因为aservice 非aop 代理对象 ,所以此时二级缓存中放入的是aservice 的普通对象;
- bservice 的bean 完成初始化;
- aservice 对 bservice 属性初始化完成 ,并将aservice 的bean 放入到一级缓存,并从二级缓存中删除;
- 对aservice 的bean 进行代理对象的包装,包装后的bean 与之前放入到一级缓存的bean 两个不是同一个,程序报错;
四、lazy 注解解决循环依赖问题
延迟加载可以通过将 bean 的依赖关系运行时进行注入,而不是在初始化阶段。这样,当遇到循环依赖时,spring 可以先创建需要的 bean 实例,并将其设置为代理对象,而不需要立即解决依赖关系。
@service public class aserviceimpl implements aservice { @autowired @lazy private bservice bservice; @async public void test(){ } }
lazy 延迟加载打破循环依赖; 通过其它途径生成bservice 的lazy 的代理对象,不会去走创建bservice 的代理 对象 然后注入aservice 这套流程。这样创建aservice 的单例对象并放入到单例池中,bservice 的bean 在实例化后,注入aservice bean 属性就可以从单例池中加载到aservice 的真正的bean ,而不会出现bean 对象不一致的问题。
总结
spring 通过三级缓存解决单例的循环依赖问题,singletonobjects 用来存放已经初始化完成的bean,earlysingletonobjects 用来存放早期暴露出来的半成品bean 的引用,singletonfactories 用来存放获取早期引用的 lambda表达式 工厂。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持代码网。
发表评论