1. 程式人生 > >論Spring中迴圈依賴的正確性與Bean注入的順序關係

論Spring中迴圈依賴的正確性與Bean注入的順序關係

一、前言

最近在做專案時候遇到一個奇葩問題,就是bean依賴注入的正確性與bean直接注入的順序有關係,但是正常情況下明明是和順序沒關係的啊,究竟啥情況那,不急,讓我一一道來。

二、普通Bean迴圈依賴-與注入順序無關

2.1 迴圈依賴例子與原理

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裡面:

2boolean 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然後返回

2.2 允許迴圈依賴的開關

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後發現迴圈依賴還是可以正常執行,翻看原始碼:

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:


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了,所以測試程式碼改為:

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
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,所以這是個迴圈依賴

測試:

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為如下:

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.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為如下:


<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該如何注入那?
類似下面的方式,會成功?留給大家思考^^

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方法的緣故。


加多

加多

高階 Java 攻城獅 at 阿里巴巴加多,目前就職於阿里巴巴,熱衷併發程式設計、ClassLoader,Spring等開源框架,分散式RPC框架dubbo,springcloud等;愛好音樂,運動。微信公眾號:技術原始積累。知識星球賬號:技術原始積累