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

@ 目录 前言 正文 分析 doGetBean 为什么Prototype不可以 createBean doCreateBean getEarlyBeanReference getSing...

@

前言

关于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

执行流程图

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

执行流程图1
执行流程图2

构造器的注入解决

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

解决方法

关于如果解决构造器的循环注入
https://www.baeldung.com/circular-dependencies-in-spring
这是一篇外国博文,小伙伴们可以看下

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

总结

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

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

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

  • 发表于 2020-05-11 09:03
  • 阅读 ( 194 )
  • 分类:网络文章

条评论

请先 登录 后评论
不写代码的码农
小编

篇文章

作家榜 »

  1. 小编 文章
返回顶部
部分文章转自于网络,若有侵权请联系我们删除