Springboot源码分析之Spring循环依赖揭秘
程序员文章站
2022-07-20 10:38:42
摘要: 若你是一个有经验的程序员,那你在开发中必然碰到过这种现象:事务不生效。或许刚说到这,有的小伙伴就会大惊失色了。 不是解决了循环依赖问题吗,它是怎么又会发生循环依赖的呢?,接下来就让我们一起揭秘 循环依赖的最本质原因。 Spring循环依赖流程图 Spring循环依赖发生原因 使用了具有代理特 ......
摘要:
若你是一个有经验的程序员,那你在开发中必然碰到过这种现象:事务不生效。或许刚说到这,有的小伙伴就会大惊失色了。spring
不是解决了循环依赖问题吗,它是怎么又会发生循环依赖的呢?,接下来就让我们一起揭秘spring
循环依赖的最本质原因。
spring循环依赖流程图
spring循环依赖发生原因
- 使用了具有代理特性的beanpostprocessor
- 典型的有 事务注解@transactional,异步注解@async等
源码分析揭秘
protected object docreatebean( ... ){ ... boolean earlysingletonexposure = (mbd.issingleton() && this.allowcircularreferences && issingletoncurrentlyincreation(beanname)); if (earlysingletonexposure) { addsingletonfactory(beanname, () -> getearlybeanreference(beanname, mbd, bean)); } ... // populatebean这一句特别的关键,它需要给a的属性赋值,所以此处会去实例化b~~ // 而b我们从上可以看到它就是个普通的bean(并不需要创建代理对象),实例化完成之后,继续给他的属性a赋值,而此时它会去拿到a的早期引用 // 也就在此处在给b的属性a赋值的时候,会执行到上面放进去的bean a流程中的getearlybeanreference()方法 从而拿到a的早期引用~~ // 执行a的getearlybeanreference()方法的时候,会执行自动代理创建器,但是由于a没有标注事务,所以最终不会创建代理,so b合格属性引用会是a的**原始对象** // 需要注意的是:@async的代理对象不是在getearlybeanreference()中创建的,是在postprocessafterinitialization创建的代理 // 从这我们也可以看出@async的代理它默认并不支持你去循环引用,因为它并没有把代理对象的早期引用提供出来~~~(注意这点和自动代理创建器的区别~) // 结论:此处给a的依赖属性字段b赋值为了b的实例(因为b不需要创建代理,所以就是原始对象) // 而此处实例b里面依赖的a注入的仍旧为bean a的普通实例对象(注意 是原始对象非代理对象) 注:此时exposedobject也依旧为原始对象 populatebean(beanname, mbd, instancewrapper); // 标注有@async的bean的代理对象在此处会被生成~~~ 参照类:asyncannotationbeanpostprocessor // 所以此句执行完成后 exposedobject就会是个代理对象而非原始对象了 exposedobject = initializebean(beanname, exposedobject, mbd); ... // 这里是报错的重点~~~ if (earlysingletonexposure) { // 上面说了a被b循环依赖进去了,所以此时a是被放进了二级缓存的,所以此处earlysingletonreference 是a的原始对象的引用 // (这也就解释了为何我说:如果a没有被循环依赖,是不会报错不会有问题的 因为若没有循环依赖earlysingletonreference =null后面就直接return了) object earlysingletonreference = getsingleton(beanname, false); if (earlysingletonreference != null) { // 上面分析了exposedobject 是被@aysnc代理过的对象, 而bean是原始对象 所以此处不相等 走else逻辑 if (exposedobject == bean) { exposedobject = earlysingletonreference; } // allowrawinjectiondespitewrapping 标注是否允许此bean的原始类型被注入到其它bean里面,即使自己最终会被包装(代理) // 默认是false表示不允许,如果改为true表示允许,就不会报错啦。这是我们后面讲的决方案的其中一个方案~~~ // 另外dependentbeanmap记录着每个bean它所依赖的bean的map~~~~ else if (!this.allowrawinjectiondespitewrapping && hasdependentbean(beanname)) { // 我们的bean a依赖于b,so此处值为["b"] string[] dependentbeans = getdependentbeans(beanname); set<string> actualdependentbeans = new linkedhashset<>(dependentbeans.length); // 对所有的依赖进行一一检查~ 比如此处b就会有问题 // “b”它经过removesingletonifcreatedfortypecheckonly最终返返回false 因为alreadycreated里面已经有它了表示b已经完全创建完成了~~~ // 而b都完成了,所以属性a也赋值完成儿聊 但是b里面引用的a和主流程我这个a竟然不相等,那肯定就有问题(说明不是最终的)~~~ // so最终会被加入到actualdependentbeans里面去,表示a真正的依赖~~~ for (string dependentbean : dependentbeans) { if (!removesingletonifcreatedfortypecheckonly(dependentbean)) { actualdependentbeans.add(dependentbean); } } // 若存在这种真正的依赖,那就报错了~~~ 则个异常就是上面看到的异常信息 if (!actualdependentbeans.isempty()) { throw new beancurrentlyincreationexception(beanname, "bean with name '" + beanname + "' has been injected into other beans [" + stringutils.collectiontocommadelimitedstring(actualdependentbeans) + "] 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 " + "'getbeannamesoftype' with the 'alloweagerinit' flag turned off, for example."); } } } } ... }
问题简化
- 发生循环依赖时候
object earlysingletonreference = getsingleton(beanname, false);
肯定有值 - 缓存工厂
addsingletonfactory(beanname, () -> getearlybeanreference(beanname, mbd, bean));
将给实例对象添加smartinstantiationawarebeanpostprocessor
-
abstractautoproxycreator
是smartinstantiationawarebeanpostprocessor
的子类,一定记住了,一定记住,smartinstantiationawarebeanpostprocessor
的子类很关键!!!!! -
exposedobject = initializebean(beanname, exposedobject, mbd);
进行beanpostprocessor
后置处理,注意是beanpostprocessor
!!!!!
spring
的循环依赖被它的三级缓存给轻易解决了,但是这2个地方的后置处理带来了 循环依赖的问题。
对比abstractadvisorautoproxycreator和asyncannotationbeanpostprocessor
由于smartinstantiationawarebeanpostprocessor
的子类会在两处都会执行后置处理,所以前后都会相同的对象引用,不会发生循环依赖问题,异步注解就不行了 ,至于为什么?自己看上面的分析,仔细看哦!
如何解决循环依赖?
- 改变加载顺序
-
@lazy
注解 -
allowrawinjectiondespitewrapping
设置为true
(利用了判断的那条语句) - 别使用相关的
beanpostprocessor
设计到的注解,,哈哈 这不太现实。
@lazy
@lazy
一般含义是懒加载,它只会作用于beandefinition.setlazyinit()
。而此处给它增加了一个能力:延迟处理(代理处理)
// @since 4.0 出现得挺晚,它支持到了@lazy 是功能最全的autowirecandidateresolver public class contextannotationautowirecandidateresolver extends qualifierannotationautowirecandidateresolver { // 这是此类本身唯一做的事,此处精析 // 返回该 lazy proxy 表示延迟初始化,实现过程是查看在 @autowired 注解处是否使用了 @lazy = true 注解 @override @nullable public object getlazyresolutionproxyifnecessary(dependencydescriptor descriptor, @nullable string beanname) { // 如果islazy=true 那就返回一个代理,否则返回null // 相当于若标注了@lazy注解,就会返回一个代理(当然@lazy注解的value值不能是false) return (islazy(descriptor) ? buildlazyresolutionproxy(descriptor, beanname) : null); } // 这个比较简单,@lazy注解标注了就行(value属性默认值是true) // @lazy支持标注在属性上和方法入参上~~~ 这里都会解析 protected boolean islazy(dependencydescriptor descriptor) { for (annotation ann : descriptor.getannotations()) { lazy lazy = annotationutils.getannotation(ann, lazy.class); if (lazy != null && lazy.value()) { return true; } } methodparameter methodparam = descriptor.getmethodparameter(); if (methodparam != null) { method method = methodparam.getmethod(); if (method == null || void.class == method.getreturntype()) { lazy lazy = annotationutils.getannotation(methodparam.getannotatedelement(), lazy.class); if (lazy != null && lazy.value()) { return true; } } } return false; } // 核心内容,是本类的灵魂~~~ protected object buildlazyresolutionproxy(final dependencydescriptor descriptor, final @nullable string beanname) { assert.state(getbeanfactory() instanceof defaultlistablebeanfactory, "beanfactory needs to be a defaultlistablebeanfactory"); // 这里毫不客气的使用了面向实现类编程,使用了defaultlistablebeanfactory.doresolvedependency()方法~~~ final defaultlistablebeanfactory beanfactory = (defaultlistablebeanfactory) getbeanfactory(); //targetsource 是它实现懒加载的核心原因,在aop那一章节了重点提到过这个接口,此处不再叙述 // 它有很多的著名实现如hotswappabletargetsource、singletontargetsource、lazyinittargetsource、 //simplebeantargetsource、threadlocaltargetsource、prototypetargetsource等等非常多 // 此处因为只需要自己用,所以采用匿名内部类的方式实现~~~ 此处最重要是看gettarget方法,它在被使用的时候(也就是代理对象真正使用的时候执行~~~) targetsource ts = new targetsource() { @override public class<?> gettargetclass() { return descriptor.getdependencytype(); } @override public boolean isstatic() { return false; } // gettarget是调用代理方法的时候会调用的,所以执行每个代理方法都会执行此方法,这也是为何doresolvedependency // 我个人认为它在效率上,是存在一定的问题的~~~所以此处建议尽量少用@lazy~~~ //不过效率上应该还好,对比http、序列化反序列化处理,简直不值一提 所以还是无所谓 用吧 @override public object gettarget() { object target = beanfactory.doresolvedependency(descriptor, beanname, null, null); if (target == null) { class<?> type = gettargetclass(); // 对多值注入的空值的友好处理(不要用null) if (map.class == type) { return collections.emptymap(); } else if (list.class == type) { return collections.emptylist(); } else if (set.class == type || collection.class == type) { return collections.emptyset(); } throw new nosuchbeandefinitionexception(descriptor.getresolvabletype(), "optional dependency not present for lazy injection point"); } return target; } @override public void releasetarget(object target) { } }; // 使用proxyfactory 给ts生成一个代理 // 由此可见最终生成的代理对象的目标对象其实是targetsource,而targetsource的目标才是我们业务的对象 proxyfactory pf = new proxyfactory(); pf.settargetsource(ts); class<?> dependencytype = descriptor.getdependencytype(); // 如果注入的语句是这么写的private ainterface a; 那这类就是借口 值是true // 把这个接口类型也得放进去(不然这个代理都不属于这个类型,反射set的时候岂不直接报错了吗????) if (dependencytype.isinterface()) { pf.addinterface(dependencytype); } return pf.getproxy(beanfactory.getbeanclassloader()); } }
标注有@lazy
注解完成注入的时候,最终注入只是一个此处临时生成的代理对象,只有在真正执行目标方法的时候才会去容器内拿到真是的bean
实例来执行目标方法。
利用allowrawinjectiondespitewrapping属性来强制改变判断
@component public class mybeanfactorypostprocessor implements beanfactorypostprocessor { @override public void postprocessbeanfactory(configurablelistablebeanfactory beanfactory) throws beansexception { ((abstractautowirecapablebeanfactory) beanfactory).setallowrawinjectiondespitewrapping(true); } }
这样会导致容器里面的是代理对象,暴露给其他实例的是原始引用,导致不生效了。由于它只对循环依赖内的bean
受影响,所以影响范围并不是全局,因此当找不到更好办法的时候,此种这样也不失是一个不错的方案。