Spring 循环引用(三)源码深入分析版
@
前言
关于spring 循环引用 网上的分析文章很多,写的水平良莠不齐,虽然看完了 知道怎么个回事 但是过段时间还是忘记了,主要本人没过目不忘的本领哈,但是只要记住主要的点就好了
但是如果你自己想更深入的了解,还是要自己去看源码分析一波,因为别人分析的时候,有些知识点你是get不到的,只有当自己走进源码去看的时候,才有get到更多的!比如网上很多文章都分析springs是怎么解决循环依赖的 但是为什么只有单类的才可以,prototype的就不行呢,在哪里不行,或者说构造器的注入为什么也不可以,最后如果解决循环依赖,或者说 怎么去换中写法去解决问题。
纸上得来终觉浅 绝知此事要躬行! 这句话献给正在读文章的你,看完记得点赞,还有就是自己去下载spring 源码 去看看
正文
ok,进入正文,当然上面也不是废话啦,spring 的循环引用 我想读者们应该知道,不知道的话,算了 来个code把!
@component public class cycletestservicea { private cycletestserviceb b; public void setb(cycletestserviceb b) { this.b = b; } } @component public class cycletestserviceb { private cycletestservicea a; public void seta(cycletestservicea a) { this.a = a; } }
上面的 代码 就是一个普通的set注入的方式,a里面依赖b,b里面依赖a,这样就导致了循环依赖,component默认是singleton的
分析
我们从spring beanc创建开始作为入口,在spring ioc 容器中一个完整的bean 要进过实例化 和初始化的阶段
spring bean 实例化就getbean的过程
那我们接进入源码去看下getbean的过程
dogetbean
getbean方法时 beanfactory 接口的方法 他的实现类有很多,我们跟进去他的抽象实现类org/springframework/beans/factory/support/abstractbeanfactory.java 类,其实都是调用了dogetbean方法
下面是我截取的核心代码
protected <t> t dogetbean( final string name, final class<t> requiredtype, final object[] args, boolean typecheckonly) throws beansexception { final string beanname = transformedbeanname(name); object bean; /* * 检测是否 有缓存对象 这个方法时处理循环依赖的关键入口 * 记住这个的代码 我还会回来的 * */ object sharedinstance = getsingleton(beanname); if (sharedinstance != null && args == null) { if (logger.isdebugenabled()) { if (issingletoncurrentlyincreation(beanname)) { logger.debug("returning eagerly cached instance of singleton bean '" + beanname + "' that is not fully initialized yet - a consequence of a circular reference"); } else { logger.debug("returning cached instance of singleton bean '" + beanname + "'"); } } bean = getobjectforbeaninstance(sharedinstance, name, beanname, null); } else { /* *prototype bean 是否在创建当中 如果存在 说明产生了循环依赖 处理bean 循环依赖的地方 *这个地方就是为什么scope 是prototype的时候 会报循环依赖的错误,慢慢看 后面会解释 * */ if (isprototypecurrentlyincreation(beanname)) { throw new beancurrentlyincreationexception(beanname); } ... if (!typecheckonly) { markbeanascreated(beanname);//这个方法就是把当前的bean 加入到alreadycreated的set集合中 后面有些判断需要 } try { ... /* * 获取bean 的依赖项 这边的依赖 是我们在xml 有时候可以配置的depends-on的依赖 和我们本次讲的循环依赖不是同一个 * 我特别说明下 * */ string[] dependson = mbd.getdependson(); if (dependson != null) { for (string dep : dependson) { //注册依赖 创建bean 等 } } /* * 如果是单列 创建createbean 记住这个的代码 我还会回来的 * */ if (mbd.issingleton()) { sharedinstance = getsingleton(beanname, new objectfactory<object>() { @override public object getobject() throws beansexception { try { return createbean(beanname, mbd, args); } catch (beansexception ex) { ... } } }); bean = getobjectforbeaninstance(sharedinstance, name, beanname, mbd); } /* * prototype对象 * */ else if (mbd.isprototype()) { // it's a prototype -> create a new instance. object prototypeinstance = null; try { beforeprototypecreation(beanname); prototypeinstance = createbean(beanname, mbd, args); } finally { afterprototypecreation(beanname); } bean = getobjectforbeaninstance(prototypeinstance, name, beanname, mbd); } /* * 不是singleton也不是prototype,可能是自定义scope的对象 * */ else { ... } } } ... return (t) bean; }
上面是dogetbean()的核心方法
为什么prototype不可以
带着这个问题 我们可以从上面的代码中 看下 spring在处理么prototype的时候 有2个方法beforeprototypecreation(),afterprototypecreation(),
上下代码
/** names of beans that are currently in creation */ private final threadlocal<object> prototypescurrentlyincreation = new namedthreadlocal<object>("prototype beans currently in creation"); protected void beforeprototypecreation(string beanname) { object curval = this.prototypescurrentlyincreation.get(); if (curval == null) { this.prototypescurrentlyincreation.set(beanname); } else if (curval instanceof string) { set<string> beannameset = new hashset<string>(2); beannameset.add((string) curval); beannameset.add(beanname); this.prototypescurrentlyincreation.set(beannameset); } else { set<string> beannameset = (set<string>) curval; beannameset.add(beanname); } } protected void afterprototypecreation(string beanname) { object curval = this.prototypescurrentlyincreation.get(); if (curval instanceof string) { this.prototypescurrentlyincreation.remove(); } else if (curval instanceof set) { set<string> beannameset = (set<string>) curval; beannameset.remove(beanname); if (beannameset.isempty()) { this.prototypescurrentlyincreation.remove(); } } }
上面的代码 我相信小伙伴都能看的懂,就是用一个set集合存储当前正在创建的bean的beanname,而且是用threadlocal去存储set集合的 threadlocal是每个线程私有的。看到这个 我们再把目光往代码上面看一看 isprototypecurrentlyincreation这个方法的判断
protected boolean isprototypecurrentlyincreation(string beanname) { object curval = this.prototypescurrentlyincreation.get(); return (curval != null && (curval.equals(beanname) || (curval instanceof set && ((set<?>) curval).contains(beanname)))); }
看到了么 这边就是用这个threadlocal里面的set集合去判断的,为什么用threadlocal想下,你想呀,a依赖b,而b依赖a,ab都是prototype的,a创建的时候 a会加入到这个set集合中,然后a去填充实例的时候,因为要依赖b,所以去getb,发现b又依赖a,这个时候有要geta,你看 当执行到 最上面的判断isprototypecurrentlyincreation的时候,是不报了循环引用的错,因为a已经在prototypescurrentlyincreation的set集合中了,因为整个流程一定是一个线程走下去的,所以存入threadlocal中,一点问题没有,而且还不受其他线程影响~
createbean
不管是哪种scope 都是要调用createbean方法的,我们跟进去代码 发现唯一重写的实现在org/springframework/beans/factory/support/abstractautowirecapablebeanfactory.java 中
我们进入代码看下
protected object createbean(string beanname, rootbeandefinition mbd, object[] args) throws beancreationexception { rootbeandefinition mbdtouse = mbd; ... try { // give beanpostprocessors a chance to return a proxy instead of the target bean instance. // 该函数的作用是给 beanpostprocessors 后置处理器返回一个代理对象的机会 // 这里是实现aop处理的重要地方 // aop是通过beanpostprocessor机制实现的,而接口instantiationawarebeanpostprocessor是实现代理的重点 object bean = resolvebeforeinstantiation(beanname, mbdtouse); if (bean != null) { return bean; } } ... /* * 后置处理器 没有返回有效的bean 就创建 * */ object beaninstance = docreatebean(beanname, mbdtouse, args); if (logger.isdebugenabled()) { logger.debug("finished creating instance of bean '" + beanname + "'"); } return beaninstance; }
这边 我看到一句英文注释,都没舍得替换中文,give beanpostprocessors a chance to return a proxy instead of the target bean instance. 哈哈 给后置处理器一个返回代理bean的机会,这边就是spring 中实现aop的重点,动态代理 其实就是使用后置处理器 替换了target bean 的实例,从而达到代理的作用,这个以后聊到aop 的时候在慢慢聊吧!这个最核心的代码还在再docreatebean中,继续跟进
docreatebean
protected object docreatebean(final string beanname, final rootbeandefinition mbd, final object[] args) throws beancreationexception { // instantiate the bean. beanwrapper instancewrapper = null;//beanwrapper 是bean 的包装类 方便对bean 实例的操作 if (mbd.issingleton()) { instancewrapper = this.factorybeaninstancecache.remove(beanname); } if (instancewrapper == null) { instancewrapper = createbeaninstance(beanname, mbd, args); } final object bean = (instancewrapper != null ? instancewrapper.getwrappedinstance() : null); class<?> beantype = (instancewrapper != null ? instancewrapper.getwrappedclass() : null); mbd.resolvedtargettype = beantype; .... boolean earlysingletonexposure = (mbd.issingleton() && this.allowcircularreferences && issingletoncurrentlyincreation(beanname));//满足三个条件 单列 运行循环引用 bean 是否正在创建中 if (earlysingletonexposure) { addsingletonfactory(beanname, new objectfactory<object>() { @override public object getobject() throws beansexception { return getearlybeanreference(beanname, mbd, bean);//提前暴露引用 获取早期的引用 } }); } // initialize the bean instance. 初始化bean 实例 object exposedobject = bean; try { populatebean(beanname, mbd, instancewrapper);//填充bean if (exposedobject != null) { exposedobject = initializebean(beanname, exposedobject, mbd);//执行初始化bean里面的方法 } } ... if (earlysingletonexposure) { object earlysingletonreference = getsingleton(beanname, false); if (earlysingletonreference != null) { /* *这边其实还是做了一个判断,exposedobject是经过了 initializebean方法方法的 *而bean还是那个提前暴露的bean, *为什么要做这个判断你,是因为exposedobject经过了initializebean里面的后置处理器的修改 可能object 已经改变了 **/ if (exposedobject == bean) { exposedobject = earlysingletonreference; } else if (!this.allowrawinjectiondespitewrapping && hasdependentbean(beanname)) { string[] dependentbeans = getdependentbeans(beanname); set<string> actualdependentbeans = new linkedhashset<string>(dependentbeans.length); for (string dependentbean : dependentbeans) { if (!removesingletonifcreatedfortypecheckonly(dependentbean)) { actualdependentbeans.add(dependentbean); } } /* *有兴趣的可以根据到上面的每一个方法看下 ,这边就是判断如果提前暴露的bean已经和在后置处理器里面修改了并且不一样了,就抛出异常,因为提前暴露的bean 可能作为了另外的bean的依赖 这样就会导致单类的bean在容器中有2个实例的出现,这是非法的! */ if (!actualdependentbeans.isempty()) { //抛出一个异常 由于很多文字我就删掉了 } } } } ... return exposedobject; }
earlysingletonexposure这个主要关注的是earlysingletonexposure 这边的代码,这个就是在bean 实例化完成后,开始填充属性之间发的代码
earlysingletonexposure为true 要满足三个条件
- 单类
- 允许循环引用
- 当时单类正在创建中
前面2个可以理解 那最后一个又是什么呢?话不多说 进入方法看下
private final set<string> singletonscurrentlyincreation = collections.newsetfrommap(new concurrenthashmap<string, boolean>(16)); public boolean issingletoncurrentlyincreation(string beanname) { return this.singletonscurrentlyincreation.contains(beanname); }
这个方法 一看很简答 就是判断当前的bean是否在singletonscurrentlyincreation的set集合中 那这个集合又是什么时候加入的呢?带着这个想法 我又重头扫描了一篇代码 还记的org/springframework/beans/factory/support/abstractbeanfactory.java代码中的dogetbean()方法里面singleton的bean 在创建instance的时候是调用了getsingleton方法么,不清楚的话 可以往上看下
getearlybeanreference
这个方法 是 addsingletonfactory 方法 构建objectfactory的参数的时候 里面返回使用方法
看下代码:
protected object getearlybeanreference(string beanname, rootbeandefinition mbd, object bean) { object exposedobject = bean; if (bean != null && !mbd.issynthetic() && hasinstantiationawarebeanpostprocessors()) { for (beanpostprocessor bp : getbeanpostprocessors()) { if (bp instanceof smartinstantiationawarebeanpostprocessor) { smartinstantiationawarebeanpostprocessor ibp = (smartinstantiationawarebeanpostprocessor) bp; exposedobject = ibp.getearlybeanreference(exposedobject, beanname); if (exposedobject == null) { return null; } } } } return exposedobject; }
里面最主要的就是看下getearlybeanreference方法 这个方法时smartinstantiationawarebeanpostprocessor里面的方法,他的实现有2个 一个是org/springframework/beans/factory/config/instantiationawarebeanpostprocessoradapter.java 还有一个是动态代理使用的 我就不列举了,
public object getearlybeanreference(object bean, string beanname) throws beansexception { return bean; }
看了下 其实instantiationawarebeanpostprocessoradapter的重写就是 返回了当前的bean 没有做任何操作。这边其实就是做了一个引用的保存。
getsingleton
代码位于org/springframework/beans/factory/support/defaultlistablebeanfactory.java中
public object getsingleton(string beanname, objectfactory<?> singletonfactory) { synchronized (this.singletonobjects) { object singletonobject = this.singletonobjects.get(beanname); if (singletonobject == null) { ... beforesingletoncreation(beanname); boolean newsingleton = false; ... try { singletonobject = singletonfactory.getobject(); newsingleton = true; } finally { aftersingletoncreation(beanname); } if (newsingleton) { addsingleton(beanname, singletonobject); } } return (singletonobject != null_object ? singletonobject : null); } }
这个方法其所就是singletonbean的核心创建流程
beforesingletoncreation
protected void beforesingletoncreation(string beanname) { if (!this.increationcheckexclusions.contains(beanname) && !this.singletonscurrentlyincreation.add(beanname)) { throw new beancurrentlyincreationexception(beanname); } }
当我看到这个singletonscurrentlyincreation.add的时候 我很欣慰 因为我之前的问题解决了 就是这个方法把bean 放入到之前的singletonscurrentlyincreation的集合中的
singletonfactory.getobject
这个应该都很清楚了 就是我们方法传入的匿名的objectfactory对象,当之前getobject的时候 才会执行我们刚才的看的createbean方法
aftersingletoncreation
protected void aftersingletoncreation(string beanname) { if (!this.increationcheckexclusions.contains(beanname) && !this.singletonscurrentlyincreation.remove(beanname)) { throw new illegalstateexception("singleton '" + beanname + "' isn't currently in creation"); } }
看下aftersingletoncreation方法里面的东西也很简单,就是从singletonscurrentlyincreation集合中移除
addsingleton
先看下代码
protected void addsingleton(string beanname, object singletonobject) { synchronized (this.singletonobjects) { this.singletonobjects.put(beanname, (singletonobject != null ? singletonobject : null_object)); this.singletonfactories.remove(beanname); this.earlysingletonobjects.remove(beanname); this.registeredsingletons.add(beanname); } }
看到 这边 就不得表介绍下三级缓存了
- singletonobjects 实例化 初始化都完成的bean 缓存
- earlysingletonobjects 可提前引用的 bean 缓存,这里面的bean 是一个非完整的bean,属性填充 后置处理器都未执行的bean
- singletonfactories 单类bean的创建工厂函数对象
说道这里 我们就清楚了 这个方法其所就是在二级缓存和三级缓存中删除当前的bean,把当前的bean 放入到一级缓存中,因为到了这一步 bean 的实例化,属性填充,后置处理器执行,初始化等方法都已经执行了。
addsingletonfactory
这个方法 哪里用的呢 那我们有要回到上面的代码docreatebean中 当earlysingletonexposure为true的时候 会调用这个方法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)) { this.singletonfactories.put(beanname, singletonfactory); this.earlysingletonobjects.remove(beanname); this.registeredsingletons.add(beanname); } } }
看下这个方法 说白了就是往三级缓存里面存放bean的objectfactory对象 这个地方也是处理循环引用的关键,这个时候bean 刚刚进行了实例化 还没有进行bean的属性填充和初始化等一些列方法
那怎么去解决提前引用的呢?可以看下objectfactory返回的是getearlybeanreference对象
getsingleton(beanname)
这个方法是在dogetbean方法中 从缓存中获取bean 对象的方法 这个方法很关键 是处理循环依赖的入口,那我们跟进去看下方法
protected object getsingleton(string beanname, boolean allowearlyreference) { object singletonobject = this.singletonobjects.get(beanname); if (singletonobject == null && issingletoncurrentlyincreation(beanname)) { synchronized (this.singletonobjects) { singletonobject = this.earlysingletonobjects.get(beanname); if (singletonobject == null && allowearlyreference) { objectfactory<?> singletonfactory = this.singletonfactories.get(beanname); if (singletonfactory != null) { singletonobject = singletonfactory.getobject(); this.earlysingletonobjects.put(beanname, singletonobject); this.singletonfactories.remove(beanname); } } } } return (singletonobject != null_object ? singletonobject : null); }
最终调用的方法如上,allowearlyreference是为true的,我们还是用最上面的servicea和serviceb 为例,第一次servicea 进入的时候 是没法进入下面的判断的 应为当前servicea不在singletoncurrentlyincreation中,但是当第二次进来,第二次是什么时候呢,就是在填充serviceb的时候 需要依赖 serviceb,这个时候serviceb也要执行getbean的流程,发现又依赖servicea,这个时候 servicea就是在singletoncurrentlyincreation的集合中了,而且在三级缓存中,这个时候会进行判断条件里面的方法,先找一级缓存,找不到就找二级缓存,最后找三级缓存,然后将取出三级缓存里面的objectfactory执行getobject方法 就是获取我们上面提到的提前引用的bean,最后将bean 放入到二级缓存,从三级缓存中移除~
核心说明
看完了 上面的一推 也许很懵逼,可能也是我文字组织能力差,只能以后慢慢改变
缓存的说明
上面涉及到几个缓存 我在边在重写描述一下
名称 | 类型 | 使用说明 | 所属类 |
---|---|---|---|
singletonobjects | map<string, object> | 实例化 初始化都完成的bean的缓存 | defaultsingletonbeanregistry.java |
earlysingletonobjects | map<string, object> | 可提前引用的 bean 缓存,这里面的bean 是一个非完整的bean,属性填充 后置处理器都未执行的bean | defaultsingletonbeanregistry.java |
singletonfactories | map<string, objectfactory<?>> | 单类bean的创建工厂函数对象 | defaultsingletonbeanregistry.java |
singletonscurrentlyincreation | set |
马上要创建的单类bean的集合 | defaultsingletonbeanregistry.java |
prototypescurrentlyincreation | threadlocal | object 是一个set |
abstractbeanfactory.java |
alreadycreated | set |
至少创建过一次的bean 在提前暴露的bean修改了导致不一致时 判断会用到 | abstractbeanfactory.java |
执行流程图
最终我还是用一个方法执行的流程图 来描述下 循环依赖的处理
构造器的注入解决
那么为什么构造器的注入方式不行呢?原因是因为 bean在实例化阶段的时候createbeaninstance的时候就会去创建依赖的b,这样的话a根本就走不到提前暴露的代码块,所以会报一个循环引用的错误,报错的地方就是构造函数参数bean 创建的地方,自己可以写个demo,调试下 在哪一步报错,博主可是看了半天 才找到,哈哈!
解决方法
关于如果解决构造器的循环注入
这是一篇外国博文,小伙伴们可以看下
- 使用懒加载
- 修改使用setter注入的方式
- 使用postconstruct注解
- initializingbean 后置处理器的方式
总结
spring 处理循环依赖的核心就是 三级缓存,让bean 提前暴露出来,可以提前引用,让互相依赖的bean 可以流程上执行下去,从而解决了循环依赖的问题
最后的最后 还是自己对照源码 自己理解一遍,我相信一定会加深你的理解,一定会有收获
码字不易,花了一个周末的时间,各位看官喜欢的话点个赞,鼓励下博主,继续创造,多谢~
上一篇: 利用Bootstrap框架制作WordPress自适应主题
下一篇: 前端工程化的意义