欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Spring 循环引用(三)源码深入分析版

程序员文章站 2022-08-09 15:56:06
@ "TOC" 前言 关于Spring 循环引用 网上的分析文章很多,写的水平良莠不齐,虽然看完了 知道怎么个回事 但是过段时间还是忘记了,主要本人没过目不忘的本领哈,但是只要记住主要的点就好了 但是如果你自己想更深入的了解,还是要自己去看源码分析一波,因为别人分析的时候,有些知识点你是get不到的 ......

@

前言

关于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 马上要创建的prototype的bean的集合 abstractbeanfactory.java
alreadycreated set 至少创建过一次的bean 在提前暴露的bean修改了导致不一致时 判断会用到 abstractbeanfactory.java

执行流程图

最终我还是用一个方法执行的流程图 来描述下 循环依赖的处理

Spring 循环引用(三)源码深入分析版
Spring 循环引用(三)源码深入分析版

构造器的注入解决

那么为什么构造器的注入方式不行呢?原因是因为 bean在实例化阶段的时候createbeaninstance的时候就会去创建依赖的b,这样的话a根本就走不到提前暴露的代码块,所以会报一个循环引用的错误,报错的地方就是构造函数参数bean 创建的地方,自己可以写个demo,调试下 在哪一步报错,博主可是看了半天 才找到,哈哈!

解决方法

关于如果解决构造器的循环注入

这是一篇外国博文,小伙伴们可以看下

  • 使用懒加载
  • 修改使用setter注入的方式
  • 使用postconstruct注解
  • initializingbean 后置处理器的方式

总结

spring 处理循环依赖的核心就是 三级缓存,让bean 提前暴露出来,可以提前引用,让互相依赖的bean 可以流程上执行下去,从而解决了循环依赖的问题

最后的最后 还是自己对照源码 自己理解一遍,我相信一定会加深你的理解,一定会有收获

码字不易,花了一个周末的时间,各位看官喜欢的话点个赞,鼓励下博主,继续创造,多谢~