Spring原始碼解析(十)分析一個Spring迴圈引用失敗的問題
前言:
之前我們有分析過Spring是怎麼解決迴圈引用的問題,主要思路就是三級快取;Spring在載入beanA的時候會先呼叫預設的空建構函式(在沒有指定建構函式例項化的前提下)得到一個空的例項引用物件,這個時候沒有設定任何值,但是Spring會用快取把它給提前暴露出來,讓其他依賴beanA的bean可以持有它提前暴露的引用;比如 a 依賴b ,b依賴a,並且他們都是通過預設方法例項化,那麼簡單流程是這樣的:
1. ioc例項化a,a提前暴露自己的,然後填充屬性值,在填充屬性值的時候發現有個物件b,這個時候去容器裡面取到b的引用,發現b還沒有被建立,那麼就走例項化b的流程;
2. 例項化b;流程跟a一樣;但是不同的是b填充屬性的時候,發現有引用a的例項,這個時候a已經提前暴露了自己了,所以b可以直接在容器裡面拿到a的引用;那麼b就例項化並且也初始化完成了;
3. 拿到b了之後,a就可以持有b的引用 ,整個流程就走完了;
Spring不能解決“A的構造方法中依賴了B的例項物件,同時B依賴了A的例項物件”這類問題
這篇文章我想從原始碼的角度來分析一下整個流程;並且分析一下Spring為什麼不能解決“A的構造方法中依賴了B的例項物件,同時B依賴了A的例項物件”這類問題
例子
首先建立兩個bean類;
CirculationA
有個屬性circulationB,並且有個建構函式給circulationB賦值;
public class CirculationA {
private CirculationB circulationB;
public CirculationA (CirculationB circulationB) {
this.circulationB = circulationB;
}
}
CirculationB
有個屬性circulationA,然後set方法
public class CirculationB {
private CirculationA circulationA;
public CirculationA getCirculationA() {
return circulationA;
}
public void setCirculationA (CirculationA circulationA) {
this.circulationA = circulationA;
}
}
SpringContextConfig.xml
circulationa 用給定的建構函式例項化;
circulationb 就用預設的例項化方法(預設的空建構函式)
<bean id="circulationa" class="src.bean.CirculationA">
<constructor-arg name="circulationB" ref="circulationb"/>
</bean>
<bean id="circulationb" class="src.bean.CirculationB" >
<property name="circulationA" ref="circulationa"/>
</bean>
好,例子準完畢,上面的例子是 circulationa的建構函式裡面有circulationb;然後circulationb屬性裡面有circulationa;
啟動容器!結果如下:
警告: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'circulationa' defined in class path resource [config.xml]: Cannot resolve reference to bean 'circulationb' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'circulationb' defined in class path resource [config.xml]: Cannot resolve reference to bean 'circulationa' while setting bean property 'circulationA'; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'circulationa': Requested bean is currently in creation: Is there an unresolvable circular reference?
Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'circulationa' defined in class path resource [config.xml]: Cannot resolve reference to bean 'circulationb' while setting constructor argument; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'circulationb' defined in class path resource [config.xml]: Cannot resolve reference to bean 'circulationa' while setting bean property 'circulationA'; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'circulationa': Requested bean is currently in creation: Is there an unresolvable circular reference?
Disconnected from the target VM, address: '127.0.0.1:64128', transport: 'socket'
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:359)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:108)
at org.springframework.beans.factory.support.ConstructorResolver.resolveConstructorArguments(ConstructorResolver.java:648)
at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:145)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1193)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1095)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:513)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:761)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:867)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:543)
at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:139)
at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:83)
at StartIOCUseDefaultListAbleBeanFactory.main(StartIOCUseDefaultListAbleBeanFactory.java:30)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'circulationb' defined in class path resource [config.xml]: Cannot resolve reference to bean 'circulationa' while setting bean property 'circulationA'; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'circulationa': Requested bean is currently in creation: Is there an unresolvable circular reference?
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:359)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:108)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1531)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1276)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:553)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:351)
... 17 more
Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'circulationa': Requested bean is currently in creation: Is there an unresolvable circular reference?
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.beforeSingletonCreation(DefaultSingletonBeanRegistry.java:347)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:223)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:351)
... 27 more
報錯了,Spring它解決不了這種情況
Ok,原始碼走起來: 為了節省篇幅我只貼重要程式碼
第一步,載入circulationa
AbstractBeanFactory
public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {
protected <T> T doGetBean(
final String name, final Class<T> requiredType, final Object[] args, boolean typeCheckOnly)
throws BeansException {
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, new ObjectFactory<Object>() {
@Override
public Object getObject() throws BeansException {
try {
return createBean(beanName, mbd, args);
}
}
});
}
}
public Object getSingleton(String beanName, ObjectFactory<?> singletonFactory) {
//在建立之前把beanName加入到正在建立中的屬性中singletonsCurrentlyInCreation;
//但是這個是一個set,如果之前已經加進去了,再進去就拋異常BeanCurrentlyInCreationException
//Requested bean is currently in creation: Is there an unresolvable circular reference?")提示可能存在迴圈引用
beforeSingletonCreation(beanName);
}
protected void beforeSingletonCreation(String beanName) {
if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
}
@Override
protected Object createBean(String beanName, RootBeanDefinition mbd, Object[] args) throws BeanCreationException {
Object beanInstance = doCreateBean(beanName, mbdToUse, args);
}
protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final Object[] args)
throws BeanCreationException {
instanceWrapper = createBeanInstance(beanName, mbd, args);
//.......
// Initialize the bean instance.
Object exposedObject = bean;
try {
populateBean(beanName, mbd, instanceWrapper);
if (exposedObject != null) {
exposedObject = initializeBean(beanName, exposedObject, mbd);
}
}
}
protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, Object[] args) {
Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
//因為circulationa是有建構函式的,所以使用autowireConstructor
if (ctors != null ||
mbd.getResolvedAutowireMode() == RootBeanDefinition.AUTOWIRE_CONSTRUCTOR ||
mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
return autowireConstructor(beanName, mbd, ctors, args);
}
}
//最終執行
public BeanWrapper autowireConstructor(final String beanName, final RootBeanDefinition mbd,
Constructor<?>[] chosenCtors, final Object[] explicitArgs) {
//解析建構函式引數值
minNrOfArgs = resolveConstructorArguments(beanName, mbd, bw, cargs, resolvedValues);
//......
//選擇對應的策略來例項化物件;這裡是生成正在的例項了。
//但是在這之前,構造引數要拿到
beanInstance = this.beanFactory.getInstantiationStrategy().instantiate(
mbd, beanName, this.beanFactory, constructorToUse, argsToUse);
}
}
省略….最終呼叫BeanDefinitionValueResolver
/**
* Resolve a reference to another bean in the factory.
*/
private Object resolveReference(Object argName, RuntimeBeanReference ref) {
try {
String refName = ref.getBeanName();
refName = String.valueOf(doEvaluate(refName));
if (ref.isToParent()) {
if (this.beanFactory.getParentBeanFactory() == null) {
throw new BeanCreationException(
this.beanDefinition.getResourceDescription(), this.beanName,
"Can't resolve reference to bean '" + refName +
"' in parent factory: no parent factory available");
}
//!!!這裡,要先去查詢refName的例項
return this.beanFactory.getParentBeanFactory().getBean(refName);
}
else {
Object bean = this.beanFactory.getBean(refName);
this.beanFactory.registerDependentBean(refName, this.beanName);
return bean;
}
}
catch (BeansException ex) {
throw new BeanCreationException(
this.beanDefinition.getResourceDescription(), this.beanName,
"Cannot resolve reference to bean '" + ref.getBeanName() + "' while setting " + argName, ex);
}
}
跟著上面的順序我們整理一下;
1. 啟動容器,載入circulationa,因為是建構函式生成,所以要先解析建構函式的屬性,這時候發現有引用circulationb,那麼通過getBean(circulationb)先拿到circulationb的例項;
2. 如果拿到了,則生成circulationa的例項物件返回;但是這個時候程式碼執行circulationb的載入過程了;
然後我們分析一下circulationb載入
circulationb跟circulationa差不多
載入circulationb,把它加入到正在建立的屬性中
protected void beforeSingletonCreation(String beanName) {
if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
}
然後用預設的方式建立例項;
circulationa 是rautowireConstructor(beanName, mbd, ctors, args)建立的;這個方法需要先拿到建構函式的值;所以執行了呼叫getBean(circulationb)
circulationa是呼叫了instantiateBean;這個方法不需要提前知道屬性;它用預設的建構函式生成例項;這時候的例項是沒有設定任何屬性的;
protected BeanWrapper instantiateBean(final String beanName, final RootBeanDefinition mbd) {
beanInstance = getInstantiationStrategy().instantiate(mbd, beanName, parent);
}
不過生成了例項之後,在doCreateBean方法中有一個populateBean;這個方法就是專門填充屬性值的,因為circulationb有circulationa的屬性;
所以會去容器裡面取circulationa的引用;但是circulationa這個時候還沒有成功建立例項啊;因為它還一直在等circulationb建立成功之後返回給它引用呢,返回了circulationa才能建立例項啊;
這個時候circulationb沒有拿到circulationa,那麼又會去呼叫getBean(circulationa);
大家想一想如果這樣下去就沒完沒了了啊;
所以Spring就丟擲異常了
那麼在哪裡丟擲異常呢?
在第二次呼叫getBean(circulationa)的時候會走到下面
if (!this.inCreationCheckExclusions.contains(beanName) && !this.singletonsCurrentlyInCreation.add(beanName)) {
throw new BeanCurrentlyInCreationException(beanName);
}
}
因為circulationa之前加進來過一次啊,而且沒有建立成功是不會刪除的啊;現在又add一次,因為this.singletonsCurrentlyInCreation是一個set;已經存在的再次add會返回false;那麼這段程式碼就會丟擲異常了;
Error creating bean with name 'circulationa': Requested bean is currently in creation: Is there an unresolvable circular reference?
情況就是這樣,只要是用建構函式建立一個例項,並且建構函式裡包含的值存在迴圈引用,那麼spring就會丟擲異常;
所以如果有迴圈引用的情況請避免使用建構函式的方式