ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

spring5源码阅读(四)解决循环依赖很简单

2019-09-15 15:05:38  阅读:329  来源: 互联网

标签:pvs 依赖 mbd beanName metadata bean 源码 属性 spring5


文章目录


先说一下循环依赖:
1.A依赖B;同时B依赖A;
2.A依赖B,B依赖C,C依赖A;
总之能形成B环,就是存在循环依赖。

上篇文章中spring5源码阅读(三),我们解释了一种循环依赖,就是使用@DependsOn注解修饰的,spring如果发现有这种循环依赖,就会直接抛异常。

还有一种类型的循环依赖,就是通过字段属性依赖,比如:

public class A {
	@Resource
	private B b;
}

public class B {
	@Resource
	private A a;
}

或者A依赖B,B依赖C,C依赖A,也是一样的道理。这种通过属性的循环依赖,并且是单例类型(非单例的spring也不支持循环依赖),spring默认是允许的。

1.spring 解决循环依赖的步骤

先整体描述下spring解决循环依赖的步骤,咱们就拿A依赖B,B依赖A这种最简单的说明:

1. 假设spring先扫描到了A,实例化了A之后,开始初始化A的属性,此时发现A依赖了B,那么就需要去先实例化B;

2. spring开始实例化B,但是实例化B之后,并不会着急初始化B,也就是不去初始化B的属性;并把B放到一个map中缓存起来,此时B的状态,spring称作earlyBean,就是早期的bean;

3. 完成A的初始化;

4. spring继续完成B的初始化,自初始化B的属性A时,因为此时容器中A已经存在,所以不用循环实例化A;此时直接完成B的初始化;

5. 第3不步结束后,实并没有真正完成A的初始化,因为A的属性b虽然不是空了,但是B的属性a还没有赋值,经过步骤4之后,其实A和B才真正完全初始化。

其实光说循环依赖,步骤就是上边这么简单。

2.源码分析

通过上述步骤,最开始假设spring先扫描到了A(先谁都一样),那么就开始实例化A。实例化的流程,就是上篇文章中的内容;我们直接从上篇文章的结尾,AbstractAutowireCapableBeanFactory#doCreateBean()方法开始。

2.1 addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory)

doCreateBean中实例化bean,初始化之前,有这么一段:

//省略。。。
    boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
		isSingletonCurrentlyInCreation(beanName));
	if (earlySingletonExposure) {
		if (logger.isDebugEnabled()) {
			logger.debug("Eagerly caching bean '" + beanName +
					"' to allow for resolving potential circular references");
		}
		//缓存到map
		addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
	}
	//省略。。。

这段就是未雨绸缪,不管有没有循环依赖,都缓存当前bean和器对应的ObjectFactory。

earlySingletonExposure的值这里是true,表示允许提前曝光bean;这里的early,表示早期的,为啥说早期呢,因为此时bean刚被实例化,但是还没有被初始化,也就是bean中的属性还没有被赋值。

此时的bean的状态,也就是第二个参数ObjectFactory<?> singletonFactory,至于getEarlyBeanReference的细节不展开,只要记得它表示bean的早期状态就行。

看下addSingletonFactory如何缓存的:

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);
		}
	}
}

this.singletonObjects:单例池,就是所有实例化并初始化好的bean,都回放在这里缓存;此时正在实例化A,这个单例池中肯定么有,进入if;
this.singletonFactories:ObjectFactory就是缓存在这里;
this.earlySingletonObjects:早期的bean对象,就是实例化了但是还没初始化的bean都在这里缓存。因为A没往里放过,此时这里肯定remove了个null;

其实看到这里,循环依赖的解决办法,我们基本已经清楚了,关键就是spring弄了个earlySingletonObjects;
也就是说,此时A中的属性b,在实实例化的时候,也存在这么一个状态;实例化b后,并没有着急去初始化它,直接将这种早期状态的b注入给了a;
因为b没有着急去初始化化,自然也就不存在实例化它所依赖的a的过程,也就不存在循环依赖了。

至于B何时被实例化,何时将它的早期状态缓存起来,又是如何注入给A的,继续往下看。

2.2 populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw)

在类A中,因为参数 private B b 使用了@Resource(或者@Autowried)注解修饰,因此会被初始化并注入值。
此方法的作用是填充bean的属性值,也就是初始化A的属性值,这里是参数b。

因为要初始化参数b,就必然要得到参数b的实例,必然就涉及到b的实例化;但是实例化b的时候,又依赖参数a,这就造成了循环依赖,继续往下看如何解决。

此方法主要流程就是遍历属性,使用不同的后置处理器去注入参数。

protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
	if (bw == null) {
		if (mbd.hasPropertyValues()) {
			throw new BeanCreationException(
					mbd.getResourceDescription(), beanName, "Cannot apply property values to null instance");
		} else {
			// Skip property population phase for null instance.
			return;
		}
	}

	//bean实例化之后,但是属性还没有被set之前调用InstantiationAwareBeanPostProcessor
	// Give any InstantiationAwareBeanPostProcessors the opportunity to modify the
	// state of the bean before properties are set. This can be used, for example,
	// to support styles of field injection.
	boolean continueWithPropertyPopulation = true;

	if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
		for (BeanPostProcessor bp : getBeanPostProcessors()) {
			if (bp instanceof InstantiationAwareBeanPostProcessor) {
				InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
				if (!ibp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) {
					//这里一般进不来,进来了就是不允许初始化bean的属性
					continueWithPropertyPopulation = false;
					break;
				}
			}
		}
	}

	if (!continueWithPropertyPopulation) {
		return;
	}

	PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null);

	//注入方式:按照名称或者类型;这里都不是,默认值是0
	if (mbd.getResolvedAutowireMode() == AUTOWIRE_BY_NAME || mbd.getResolvedAutowireMode() == AUTOWIRE_BY_TYPE) {
		MutablePropertyValues newPvs = new MutablePropertyValues(pvs);
		// Add property values based on autowire by name if applicable.
		if (mbd.getResolvedAutowireMode() == AUTOWIRE_BY_NAME) {
			autowireByName(beanName, mbd, bw, newPvs);
		}
		// Add property values based on autowire by type if applicable.
		if (mbd.getResolvedAutowireMode() == AUTOWIRE_BY_TYPE) {
			autowireByType(beanName, mbd, bw, newPvs);
		}
		pvs = newPvs;
	}

	boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors();
	boolean needsDepCheck = (mbd.getDependencyCheck() != AbstractBeanDefinition.DEPENDENCY_CHECK_NONE);

	if (hasInstAwareBpps || needsDepCheck) {
		if (pvs == null) {
			pvs = mbd.getPropertyValues();
		}
		PropertyDescriptor[] filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
		if (hasInstAwareBpps) {
		    //重点在这里
			for (BeanPostProcessor bp : getBeanPostProcessors()) {
				//CommonAnnotationBeanPostProcessor
				//AutowiredAnnotationBeanPostProcessor等
				if (bp instanceof InstantiationAwareBeanPostProcessor) {
					InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
					pvs = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
					if (pvs == null) {
						return;
					}
				}
			}
		}
		if (needsDepCheck) {
			//遍历检查属性是否都被set值了
			checkDependencies(beanName, mbd, filteredPds, pvs);
		}
	}

	if (pvs != null) {
		applyPropertyValues(beanName, mbd, bw, pvs);
	}
}

整个方法比较重要的是这个for循环,

for (BeanPostProcessor bp : getBeanPostProcessors()) {
	//CommonAnnotationBeanPostProcessor
	//AutowiredAnnotationBeanPostProcessor等
	if (bp instanceof InstantiationAwareBeanPostProcessor) {
		InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
		pvs = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
		if (pvs == null) {
			return;
		}
	}
}

这里遍历得到InstantiationAwareBeanPostProcessor类型的后置处理器,执行回调方法postProcessPropertyValues。

比如CommonAnnotationBeanPostProcessor.java,用于发现并缓存@Resource等注解修饰的属性;
比如AutowiredAnnotationBeanPostProcessor.java,用于发现并缓存@Autowired/@Value等注解修饰的属性;

因为我们使用了@Resource注解,因此我们继续看CommonAnnotationBeanPostProcessor的回调方法postProcessPropertyValues,@Autowired等注解修饰的,流程基本一致。

在看CommonAnnotationBeanPostProcessor#postProcessPropertyValues回调方法之前,有必要先说一下它的另外一个回调方法postProcessMergedBeanDefinition()。

2.3 CommonAnnotationBeanPostProcessor#postProcessMergedBeanDefinition()

为什么先说这个方法呢,因为这个回调方法是先被执行的。

具体位置是,在上篇文章中spring5源码阅读(三)最后一节doCreateBean方法中(populateBean方法之前),有如一下一行代码:
applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);

打开,

protected void applyMergedBeanDefinitionPostProcessors(RootBeanDefinition mbd, Class<?> beanType, String beanName) {
for (BeanPostProcessor bp : getBeanPostProcessors()) {
	if (bp instanceof MergedBeanDefinitionPostProcessor) {
		MergedBeanDefinitionPostProcessor bdp = (MergedBeanDefinitionPostProcessor) bp;
		bdp.postProcessMergedBeanDefinition(mbd, beanType, beanName);
	}
}

这段方法是在遍历执行MergedBeanDefinitionPostProcessor类型的回调方法,也会找到CommonAnnotationBeanPostProcessor,到这感觉有点乱,我们看看CommonAnnotationBeanPostProcessor类图:
在这里插入图片描述
通过类图可发现,CommonAnnotationBeanPostProcessor 既实现了MergedBeanDefinitionPostProcessor接口,又实现了InstantiationAwareBeanPostProcessor接口。

看下具体的回调方法内容:

public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
	super.postProcessMergedBeanDefinition(beanDefinition, beanType, beanName);
	InjectionMetadata metadata = findResourceMetadata(beanName, beanType, null);
	metadata.checkConfigMembers(beanDefinition);
}

记住这个findResourceMetadata(beanName, beanType, null)方法,因为下边还会执行一次,我们后边在分析。

2.4 CommonAnnotationBeanPostProcessor#postProcessPropertyValues()

上边2.3节说完了CommonAnnotationBeanPostProcessor的第一个先执行的回调方法,这继续说第二个,
进入 CommonAnnotationBeanPostProcessor的回调方法postProcessPropertyValues:

public PropertyValues postProcessPropertyValues(
		PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) {
    //InjectionMetadata就是类中注入的属性等信息
	InjectionMetadata metadata = findResourceMetadata(beanName, bean.getClass(), pvs);
	try {
		metadata.inject(bean, beanName, pvs);
	} catch (Throwable ex) {
		throw new BeanCreationException(beanName, "Injection of resource dependencies failed", ex);
	}
	return pvs;
}

此方法分为两步:

  • 利用findResourceMetadata方法,得到类中@Resource注解修饰的属性和方法,返回InjectionMetadata类型的对象;
  • 利用metadata.inject方法,对这些属性,进行值的注入,也就是赋值

findResourceMetadata()就是在2.3节中已经执行过一次的方法。

下面我们分开说。

2.4.1 findResourceMetadata() 方法

此方法的作用是拿到类中的属性和方法,当然不是所有的,是目的性的获取,比如被@Resource注解修饰的。

看代码:

private InjectionMetadata findResourceMetadata(String beanName, final Class<?> clazz, @Nullable PropertyValues pvs) {
	// Fall back to class name as cache key, for backwards compatibility with custom callers.
	String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());
	// Quick check on the concurrent map first, with minimal locking.
	//InjectionMetadata就是类中的属性等信息
	InjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);
	if (InjectionMetadata.needsRefresh(metadata, clazz)) {
		synchronized (this.injectionMetadataCache) {
			metadata = this.injectionMetadataCache.get(cacheKey);
			if (InjectionMetadata.needsRefresh(metadata, clazz)) {
				if (metadata != null) {
					metadata.clear(pvs);
				}
				//利用反射遍历类中的属性和方法,找到被@Resource等注解修饰的属性或者方法
				metadata = buildResourceMetadata(clazz);
				this.injectionMetadataCache.put(cacheKey, metadata);
			}
		}
	}
	return metadata;
}

2.3节我们说了,此方法其实在前边已经被执行过。
第一次执行的时候,metadata肯定是null,也即是缓存中没有,会进入if判断中。

简单看下if判断,返回true。

public static boolean needsRefresh(@Nullable InjectionMetadata metadata, Class<?> clazz) {
	return (metadata == null || metadata.targetClass != clazz);
}

最后会执行metadata = buildResourceMetadata(clazz)方法,此方法我们就不展开了,作用就是利用反射遍历类中的属性和方法,找到被@Resource等注解修饰的属性或者方法。

第二次执行的时候,就直接从缓存中取就行了。

2.4.2 InjectionMetadata#inject(bean, beanName, pvs)

拿到了类的属性和方法之后,这里通过inject方法进行注入操作,就是给属性赋值。
比如这里我们正在初始化类A,其中有个参数 @Resource private B b; 那么就需要把B的实例,赋值给参数b。

public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
	Collection<InjectedElement> checkedElements = this.checkedElements;
	Collection<InjectedElement> elementsToIterate =
			(checkedElements != null ? checkedElements : this.injectedElements);
	if (!elementsToIterate.isEmpty()) {
		for (InjectedElement element : elementsToIterate) {
			if (logger.isDebugEnabled()) {
				logger.debug("Processing injected element of bean '" + beanName + "': " + element);
			}
			element.inject(target, beanName, pvs);
		}
	}
}

整体思路就是遍历属性,比如我们这里只有属性b;然后执行属性的inject方法。

inject方法我们就不继续了,大致思路就是:

  • 调用了bean工厂的getBean方法,也就是上一篇文章的全部内容,最终必然会通过getEarlyBeanReference得到早期的实例a,此时的a没有被初始化;
  • 通过反射,调用属性的set方法赋值。

标签:pvs,依赖,mbd,beanName,metadata,bean,源码,属性,spring5
来源: https://blog.csdn.net/csdn_20150804/article/details/100836351

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有