最近在作項目時候遇到一個奇葩問題,就是bean依賴注入的正確性與bean直接注入的順序有關係,可是正常狀況下明明是和順序不要緊的啊,究竟啥狀況那,不急,讓我一一道來。spring
public class BeanA { private BeanB beanB; public BeanB getBeanB() { return beanB; } public void setBeanB(BeanB beanB) { this.beanB = beanB; } }
public class BeanB { private BeanA beanA; public BeanA getBeanA() { return beanA; } public void setBeanA(BeanA beanA) { this.beanA = beanA; } }
<bean id="beanA" class="com.alibaba.test.circle.BeanA"> <property name="beanB"> <ref bean="beanB" /> </property> </bean> <bean id="beanB" class="com.alibaba.test.circle.BeanB"> <property name="beanA"> <ref bean="beanA" /> </property> </bean>
上述循環依賴注入可以正常工做,這是由於Spring提供了EarlyBeanReference功能,首先Spring裏面有個名字爲singletonObjects的併發map用來存放全部實例化而且初始化好的bean,singletonFactories則用來存放須要解決循環依賴的bean信息(beanName,和一個回調工廠)。當實例化beanA時候會觸發getBean("beanA");首先看singletonObjects中是否有beanA有則返回:併發
(1) Object sharedInstance = getSingleton(beanName);//getSingleton(beanName,true); 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直接返回,工廠bean則返回sharedInstance.getObject(); bean = getObjectForBeanInstance(sharedInstance, name, beanName, null); } protected Object getSingleton(String beanName, boolean allowEarlyReference) { Object singletonObject = this.singletonObjects.get(beanName); if (singletonObject == null) { synchronized (this.singletonObjects) { singletonObject = this.earlySingletonObjects.get(beanName); if (singletonObject == null && allowEarlyReference) { ObjectFactory singletonFactory = (ObjectFactory) 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); }
一開始確定沒有因此會實例化beanA,若是設置了allowCircularReferences=true(默認爲true)而且當前bean爲單件而且該bean目前在建立中,則初始化屬性前把該bean信息放入singletonFactories單件map裏面:app
(2) 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"); } addSingletonFactory(beanName, new ObjectFactory() { public Object getObject() throws BeansException { return getEarlyBeanReference(beanName, mbd, 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); } } }
而後對該實例進行屬性注入beanB,屬性注入時候會getBean("beanB"),發現beanB 不在singletonObjects中,就會實例化beanB,而後放入singletonFactories,而後進行屬性注入beanA,而後觸發getBean("beanA");這時候會到(1)getSingleton返回實例化的beanA。到此beanB初始化完畢添加beanB 到singletonObjects而後返回,而後beanA 初始化完畢,添加beanA到singletonObjects而後返回測試
public class TestCircle2 { private final static ClassPathXmlApplicationContext moduleContext; private static Test test; static { moduleContext = new ClassPathXmlApplicationContext(new String[]{"beans-circile.xml"}); moduleContext.setAllowCircularReferences(false); test = (Test) moduleContext.getBean("test"); } public static void main(String[] args) { System.out.println(test.name); } }
ClassPathXmlApplicationContext類中有個屬性allowCircularReferences用來控制是否容許循環依賴默認爲true,這裏設置爲false後發現循環依賴仍是能夠正常運行,翻看源碼:this
public ClassPathXmlApplicationContext(String[] configLocations) throws BeansException { this(configLocations, true, null); } public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException { super(parent); setConfigLocations(configLocations); if (refresh) { refresh(); } } public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException { super(parent); setConfigLocations(configLocations); if (refresh) { refresh(); } }
知道默認構造ClassPathXmlApplicationContext時候會刷新容器。
refresh方法會調用refreshBeanFactory:spa
protected final void refreshBeanFactory() throws BeansException { if (hasBeanFactory()) { destroyBeans(); closeBeanFactory(); } try { // 建立bean工廠 DefaultListableBeanFactory beanFactory = createBeanFactory(); //定製bean工廠屬性 customizeBeanFactory(beanFactory); loadBeanDefinitions(beanFactory); synchronized (this.beanFactoryMonitor) { this.beanFactory = beanFactory; } } catch (IOException ex) { throw new ApplicationContextException( "I/O error parsing XML document for application context [" + getDisplayName() + "]", ex); } } protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) { if (this.allowBeanDefinitionOverriding != null) { beanFactory.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding.booleanValue()); } if (this.allowCircularReferences != null) { beanFactory.setAllowCircularReferences(this.allowCircularReferences.booleanValue()); } }
到這裏就知道了,咱們在調用 moduleContext.setAllowCircularReferences(false)前,spring留出的設置bean工廠的回調customizeBeanFactory已經執行過了,最終緣由是,調用設置前,bean工廠已經refresh了,因此測試代碼改成:debug
public class TestCircle { private final static ClassPathXmlApplicationContext moduleContext; private static Test test; static { //初始化容器上下文,可是不刷新容器 moduleContext = new ClassPathXmlApplicationContext(new String[]{"beans-circile.xml"},false); moduleContext.setAllowCircularReferences(false); //刷新容器 moduleContext.refresh(); test = (Test) moduleContext.getBean("test"); } public static void main(String[] args) { System.out.println(test.name); } }
如今測試就會拋出異常:code
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'beanA' defined in class path resource [beans-circile.xml]: Cannot resolve reference to bean 'beanB' while setting bean property 'beanB'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'beanB' defined in class path resource [beans-circile.xml]: Cannot resolve reference to bean 'beanA' while setting bean property 'beanA'; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'beanA': Requested bean is currently in creation: Is there an unresolvable circular reference?
工廠bean public class MyFactoryBean implements FactoryBean,InitializingBean{ private String name; private Test test; public String getName() { return name; } public void setName(String name) { this.name = name; } public DependentBean getDepentBean() { return depentBean; } public void setDepentBean(DependentBean depentBean) { this.depentBean = depentBean; } private DependentBean depentBean; public Object getObject() throws Exception { return test; } public Class getObjectType() { // TODO Auto-generated method stub return Test.class; } public boolean isSingleton() { // TODO Auto-generated method stub return true; } public void afterPropertiesSet() throws Exception { System.out.println("name:" + this.name); test = new Test(); test.name = depentBean.doSomething() + this.name; } }
爲了簡化,只寫一個public的變量 public class Test { public String name; }
public class DependentBean { public String doSomething(){ return "hello:"; } @Autowired private Test test; }
xml配置 <bean id="test" class="com.alibaba.test.circle.MyFactoryBean"> <property name="depentBean"> <bean class="com.alibaba.test.circle.DependentBean"></bean> </property> <property name="name" value="zlx"></property> </bean>
其中工廠Bean MyFactoryBean做用是對Test類的包裝,首先對MyFactoryBean設置屬性,而後在MyFactoryBean的afterPropertiesSet方法中建立一個Test實例,而且設置屬性,實例化MyFactoryBean最終會調用getObject方法返回建立的Test對象。這裏MyFactoryBean依賴了DepentBean,而depentBean自己有依賴了Test,因此這是個循環依賴xml
測試:對象
public class TestCircle2 { private final static ClassPathXmlApplicationContext moduleContext; private static Test test; static { moduleContext = new ClassPathXmlApplicationContext(new String[]{"beans-circile.xml"}); test = (Test) moduleContext.getBean("test"); } public static void main(String[] args) { System.out.println(test.name); } }
結果:
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'com.alibaba.test.circle.DependentBean#1c701a27': Autowiring of fields failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.alibaba.test.circle.Test com.alibaba.test.circle.DependentBean.test; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'test': FactoryBean which is currently in creation returned null from getObject
下面列下Spring bean建立的流程:
getBean()->建立實例->autowired->set屬性->afterPropertiesSet
也就是調用getObject方法早於afterPropertiesSet方法被調用了。
那麼咱們修改下MyFactoryBean爲以下:
public Object getObject() throws Exception { // TODO Auto-generated method stub if(null == test){ afterPropertiesSet(); } return test; } public void afterPropertiesSet() throws Exception { if(null == test){ System.out.println("name:" + this.name); test = new Test(); test.name = depentBean.doSomething() + this.name; } }
也就是getObject內部先判斷不如test==null那調用下afterPropertiesSet,而後afterPropertiesSet內部若是test==null在建立Test實例,看起來貌似不錯,好想能夠解決咱們的問題。可是實際上仍是不行的,由於afterPropertiesSet內部使用了depentBean,而此時depentBean=null。
3.2分析緣由是先建立了MyFactoryBean,並在在建立MyFactoryBean的過程當中有建立了DepentBean,而建立DepentBean時候須要autowired MyFactoryBean的實例,而後要調用afterPropertiesSet前調用getObject方法因此返回null。
那若是先建立DepentBean,而後在建立MyFactoryBean那?下面分析下過程:
按照這分析先建立DepentBean,而後在實例化MyFactoryBean是可行的,修改xml爲以下:
<bean id="dependentBean" class="com.alibaba.test.circle.DependentBean"></bean> <bean id="test" class="com.alibaba.test.circle.MyFactoryBean"> <property name="depentBean"> <ref bean="dependentBean" /> </property> <property name="name" value="zlx"></property> </bean>
測試運行結果:
name:zlx
hello:zlx
果然能夠了,那按照這分析,上面XML配置若是調整了聲明順序,確定也是會出錯的,由於test建立比dependentBean早,測試下果真如此。另外可想而知工廠bean循環依賴工廠bean時候不管聲明順序如何須然也會失敗。
上面先注入了MyFactoryBean中須要使用的dependentBean,而後注入MyFactoryBean,問題就解決了。那麼若是須要在另一個Bean中使用建立的id="test"的對象時候,這個Bean該如何注入那?
相似下面的方式,會成功?留給你們思考^^
public class UseTest { @Autowired private Test test; }
<bean id="useTest" class="com.alibaba.test.circle.UseTest"></bean> <bean id="dependentBean" class="com.alibaba.test.circle.DependentBean"></bean> <bean id="test" class="com.alibaba.test.circle.MyFactoryBean"> <property name="depentBean"> <ref bean="dependentBean" /> </property> <property name="name" value="zlx"></property> </bean>
普通Bean之間相互依賴時候Bean注入順序是沒有關係的,可是工廠Bean與普通Bean相互依賴時候則必須先實例化普通bean,這是由於工廠Bean的特殊性,也就是其有個getObject方法的緣故。