一、前言
我們知道 Spring 可以是懶加載的,就是當真正使用到 Bean 的時候才實例化 Bean。當然也不全是這樣,例如配置 Bean 的 lazy-init 屬性,可以控制 Spring 的加載時機。現在機器的性能、內存等都比較高,基本上也不使用懶加載,在容器啟動時候來加載bean,啟動時間稍微長一點兒,這樣在實際獲取 bean 供業務使用時,就可以減輕不少負擔,這個后面再做分析。 我們使用到 Bean 的時候,最直接的方式就是從 Factroy 中獲取,這個就是加載 Bean 實例的源頭。
最近在做項目時候遇到一個奇葩問題,就是bean依賴注入的正確性與bean直接注入的順序有關系,但是正常情況下明明是和順序沒關系的啊,究竟啥情況那,不急,讓我一一道來。
二、普通Bean循環依賴-與注入順序無關
2.1 循環依賴例子與原理
1
2
3
4
5
6
7
8
9
|
public class BeanA { private BeanB beanB; public BeanB getBeanB() { return beanB; } public void setBeanB(BeanB beanB) { this .beanB = beanB; } } |
1
2
3
4
5
6
7
8
9
|
public class BeanB { private BeanA beanA; public BeanA getBeanA() { return beanA; } public void setBeanA(BeanA beanA) { this .beanA = beanA; } } |
1
2
3
4
5
|
<bean id= "beanA" class = "com.alibaba.test.circle.BeanA" > <property name= "beanB" > <ref bean= "beanB" /> </property> </bean> |
1
2
3
4
5
|
<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)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
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 ); } |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
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里面:
(2)
1
2
|
boolean earlySingletonExposure = (mbd.isSingleton() && this .allowCircularReferences && isSingletonCurrentlyInCreation(beanName)); |
1
2
3
4
5
6
7
8
9
10
11
|
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); } }); } |
1
2
3
4
5
6
7
8
9
10
|
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然后返回
2.2 允許循環依賴的開關
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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后發現循環依賴還是可以正常運行,翻看源碼:
1
2
3
|
public ClassPathXmlApplicationContext(String[] configLocations) throws BeansException { this (configLocations, true , null ); } |
1
2
3
4
5
6
7
8
|
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException { super (parent); setConfigLocations(configLocations); if (refresh) { refresh(); } } |
1
2
3
4
5
6
7
8
|
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException { super (parent); setConfigLocations(configLocations); if (refresh) { refresh(); } } |
知道默認構造ClassPathXmlApplicationContext時候會刷新容器。
refresh方法會調用refreshBeanFactory:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
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); } } |
1
2
3
4
5
6
7
8
|
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了,所以測試代碼改為:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
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); } } |
現在測試就會拋出異常:
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與普通Bean循環依賴-與注入順序有關
3.1 測試代碼
工廠bean
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
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的變量
1
2
3
|
public class Test { public String name; } |
1
2
3
4
5
6
7
|
public class DependentBean { public String doSomething(){ return "hello:" ; } @Autowired private Test test; } |
xml配置
1
2
3
4
5
6
|
<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,所以這是個循環依賴
測試:
1
2
3
4
5
6
7
8
9
10
11
|
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
3.2 分析原因
當實例化test時候會觸發getBean(“test”)
,會看當前bean是否存在
不存在則創建Test 的實例,創建完畢后會把當前bean信息放入singletonFactories單件map里面
然后對該實例進行屬性注入depentBean,屬性注入時候會getBean(“depentBean”)
,
發現depentBean 不存在,就會實例化depentBean,然后放入singletonFactories,
然后進行autowired注入test,然后觸發getBean(“test”);
這時候會到(1)getSingleton返回實例化的test。由于test是工廠bean所以返回test.getObject();
而MyFactoryBean的afterPropertiesSet還沒被調用,所以test.getObject()
返回null.
下面列下Spring bean創建的流程:
getBean()->創建實例->autowired->set屬性->afterPropertiesSet
也就是調用getObject方法早于afterPropertiesSet方法被調用了。
那么我們修改下MyFactoryBean為如下:
1
2
3
4
5
6
7
|
public Object getObject() throws Exception { // TODO Auto-generated method stub if ( null == test){ afterPropertiesSet(); } return test; } |
1
2
3
4
5
6
7
|
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.3 思考如何解決
3.2分析原因是先創建了MyFactoryBean,并在在創建MyFactoryBean的過程中有創建了DepentBean,而創建DepentBean時候需要autowired MyFactoryBean的實例,然后要調用afterPropertiesSet前調用getObject方法所以返回null。
那如果先創建DepentBean,然后在創建MyFactoryBean那?下面分析下過程:
首先會實例化DepentBean,并且加入到singletonFactories
DepentBean實例會autowired Test,所以會先創建Test實例
創建Test實例,然后加入singletonFactories
Test實例會屬性注入DepentBean實例,所以會getBean(“depentBean”);
getBean(“depentBean”)
發現singletonFactories中已經有depentBean了,則返回depentBean對象
因為depentBean不是工廠bean所以直接返回depentBean
Test實例會屬性注入DepentBean實例成功,Test實例初始化OK
DepentBean實例會autowired Test實例OK
按照這分析先創建DepentBean,然后在實例化MyFactoryBean是可行的,修改xml為如下:
1
2
3
4
5
6
7
|
< 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時候無論聲明順序如何必然也會失敗。
3.3 一個思考
上面先注入了MyFactoryBean中需要使用的dependentBean,然后注入MyFactoryBean,問題就解決了。那么如果需要在另外一個Bean中使用創建的id=”test”的對象時候,這個Bean該如何注入那?
類似下面的方式,會成功?留給大家思考^^
1
2
3
4
|
public class UseTest { @Autowired private Test test; } |
1
2
3
4
5
6
7
8
|
<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方法的緣故。
好了,以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對服務器之家的支持。
原文鏈接:https://segmentfault.com/a/1190000012738048