zoukankan      html  css  js  c++  java
  • 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 马上要创建的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 可以流程上执行下去,从而解决了循环依赖的问题

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

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

  • 相关阅读:
    Alice and Bob(博弈)
    Cuckoo for Hashing(hash)hunnuoj
    Median(vector+二分)
    Open Credit System(UVA11078)
    First Date (hnoj12952)日期计算
    Inviting Friends(hdu3244 && zoj3187)完全背包+二分
    Factorial Problem in Base K(zoj3621)
    吉哥系列故事——临时工计划(dp)
    密码是我的QQ号
    Beans Game(博弈 | | DP)zoj 3057
  • 原文地址:https://www.cnblogs.com/burg-xun/p/12865205.html
Copyright © 2011-2022 走看看