联系我们 - 广告服务
您的当前位置:官网首页 > 资讯 > 时局 >

Springboot源码分析之Spring循环依赖揭秘

来源: 编辑: 时间:2019-09-11
导读: 若你是一个有经验的程序员,那你在开发中必然碰到过这种现象:事务不生效。或许刚说到这,有的小伙伴就会大惊失色了。spring不是解决了循环依赖问题吗,它是怎么又会......

若你是一个有经验的程序员,那你在开发中必然碰到过这种现象:事务不生效。或许刚说到这,有的小伙伴就会大惊失色了。spring不是解决了循环依赖问题吗,它是怎么又会发生循环依赖的呢?,接下来就让我们一起揭秘spring循环依赖的最本质原因。

file
filefilefile
 protected object docreatebean{
 boolean earlysingletonexposure =  this.allowcircularreferences issingletoncurrentlyincreation);
 if  {
 addsingletonfactory - getearlybeanreference);
 // 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里面依赖的a注入的仍旧为bean a的普通实例对象 注:此时exposedobject也依旧为原始对象
 populatebean;
 // 标注有@async的bean的代理对象在此处会被生成~~~ 参照类:asyncannotationbeanpostprocessor
 // 所以此句执行完成后 exposedobject就会是个代理对象而非原始对象了
 exposedobject = initializebean;
 // 这里是报错的重点~~~
 if  {
 // 上面说了a被b循环依赖进去了,所以此时a是被放进了二级缓存的,所以此处earlysingletonreference 是a的原始对象的引用
 // 
 object earlysingletonreference = getsingleton;
 if  {
 // 上面分析了exposedobject 是被@aysnc代理过的对象, 而bean是原始对象 所以此处不相等 走else逻辑
 if  {
 exposedobject = earlysingletonreference;
 // allowrawinjectiondespitewrapping 标注是否允许此bean的原始类型被注入到其它bean里面,即使自己最终会被包装
 // 默认是false表示不允许,如果改为true表示允许,就不会报错啦。这是我们后面讲的决方案的其中一个方案~~~
 // 另外dependentbeanmap记录着每个bean它所依赖的bean的map~~~~
 else if ) {
 // 我们的bean a依赖于b,so此处值为[ b ]
 string[] dependentbeans = getdependentbeans;
 set string actualdependentbeans = new linkedhashset ;
 // 对所有的依赖进行一一检查~ 比如此处b就会有问题
 // “b”它经过removesingletonifcreatedfortypecheckonly最终返返回false 因为alreadycreated里面已经有它了表示b已经完全创建完成了~~~
 // 而b都完成了,所以属性a也赋值完成儿聊 但是b里面引用的a和主流程我这个a竟然不相等,那肯定就有问题~~~
 // so最终会被加入到actualdependentbeans里面去,表示a真正的依赖~~~
 for  {
 if ) {
 actualdependentbeans.add;
 // 若存在这种真正的依赖,那就报错了~~~ 则个异常就是上面看到的异常信息
 if ) {
 throw new beancurrentlyincreationexception +
 ] 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. 
 }

spring的循环依赖被它的三级缓存给轻易解决了,但是这2个地方的后置处理带来了 循环依赖的问题。

对比abstractadvisorautoproxycreator和asyncannotationbeanpostprocessor

filefile

由于smartinstantiationawarebeanpostprocessor的子类会在两处都会执行后置处理,所以前后都会相同的对象引用,不会发生循环依赖问题,异步注解就不行了 ,至于为什么?自己看上面的分析,仔细看哦!


file

@lazy一般含义是懒加载,它只会作用于beandefinition.setlazyinit。而此处给它增加了一个能力:延迟处理

 // @since 4.0 出现得挺晚,它支持到了@lazy 是功能最全的autowirecandidateresolver
 public class contextannotationautowirecandidateresolver extends qualifierannotationautowirecandidateresolver {
 // 这是此类本身唯一做的事,此处精析 
 // 返回该 lazy proxy 表示延迟初始化,实现过程是查看在 @autowired 注解处是否使用了 @lazy = true 注解 
 @override
 @nullable
 public object getlazyresolutionproxyifnecessary {
 // 如果islazy=true 那就返回一个代理,否则返回null
 // 相当于若标注了@lazy注解,就会返回一个代理
 return  ? buildlazyresolutionproxy : null);
 // 这个比较简单,@lazy注解标注了就行
 // @lazy支持标注在属性上和方法入参上~~~ 这里都会解析
 protected boolean islazy {
 for ) {
 lazy lazy = annotationutils.getannotation;
 if ) {
 return true;
 methodparameter methodparam = descriptor.getmethodparameter;
 if  {
 method method = methodparam.getmethod;
 if ) {
 lazy lazy = annotationutils.getannotation, lazy.class);
 if ) {
 return true;
 return false;
 // 核心内容,是本类的灵魂~~~
 protected object buildlazyresolutionproxy {
 assert.state instanceof defaultlistablebeanfactory,
 beanfactory needs to be a defaultlistablebeanfactory 
 // 这里毫不客气的使用了面向实现类编程,使用了defaultlistablebeanfactory.doresolvedependency方法~~~
 final defaultlistablebeanfactory beanfactory =  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;
 if  {
 class ? type = gettargetclass;
 // 对多值注入的空值的友好处理
 if  {
 return collections.emptymap;
 } else if  {
 return collections.emptylist;
 } else if  {
 return collections.emptyset;
 throw new nosuchbeandefinitionexception,
 optional dependency not present for lazy injection point 
 return target;
 @override
 public void releasetarget {
 // 使用proxyfactory 给ts生成一个代理
 // 由此可见最终生成的代理对象的目标对象其实是targetsource,而targetsource的目标才是我们业务的对象
 proxyfactory pf = new proxyfactory;
 pf.settargetsource;
 class ? dependencytype = descriptor.getdependencytype;
 // 如果注入的语句是这么写的private ainterface a; 那这类就是借口 值是true
 // 把这个接口类型也得放进去
 if ) {
 pf.addinterface;
 return pf.getproxy);
 }

标注有@lazy注解完成注入的时候,最终注入只是一个此处临时生成的代理对象,只有在真正执行目标方法的时候才会去容器内拿到真是的bean实例来执行目标方法。

利用allowrawinjectiondespitewrapping属性来强制改变判断

 @component
 public class mybeanfactorypostprocessor implements beanfactorypostprocessor {
 @override
 public void postprocessbeanfactory throws beansexception {
  beanfactory).setallowrawinjectiondespitewrapping;
 }

这样会导致容器里面的是代理对象,暴露给其他实例的是原始引用,导致不生效了。由于它只对循环依赖内的bean受影响,所以影响范围并不是全局,因此当找不到更好办法的时候,此种这样也不失是一个不错的方案。

责任编辑:

打赏

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

网友评论:

在“\templets\demo\comments.htm”原来的内容全部删除,插入第三方评论代码,如果不需要评论功能,删除comments.html里面的内容即可
推荐使用友言、多说、畅言(需备案后使用)等社会化评论插件

Copyright © 2018 凯发vip凯发vip-凯发vip网址-凯发官网 All Rights Reserved Power by DedeCms
本站所有资讯来源于网络 如有侵权请联系QQ:9490489
Top