1. 程式人生 > >Spring Bean注入/單例理解/迴圈依賴

Spring Bean注入/單例理解/迴圈依賴

理解迴圈依賴問題,首先明白spring有四種注入方式。

第一種,SET注入
a類中持有b類的引用,並且a類有b的set方法。在bean中新增<property>標籤即可注入。實質上是將b例項化,然後呼叫set方法注入。


 <bean id="a" class="com.qunar.pojo.StudentA" scope="singleton">
        <property name="studentB" ref="b"></property>
    </bean>

第二種,構造器注入
a類中持有b類的引用,並且a的建構函式引數中有b。實質上就是通過建構函式注入,建立a物件時要把b物件傳進去。


  <bean id="a" class="com.qunar.pojo.StudentA">
        <constructor-arg index="0" ref="b"></constructor-arg>
    </bean>

第三種,靜態工廠
如果有需要靜態工廠例項化的類,不能通過靜態工廠.方法實現。在bean屬性中對應類指向靜態工廠,對應方法指向返回例項的方法
Spring Bean注入/單例理解/迴圈依賴
Spring Bean注入/單例理解/迴圈依賴

第四種,例項工廠
如果工廠不是靜態,需要例項化,就例項化對應工廠,設定factory-bean和factory-method進行方法呼叫。
Spring Bean注入/單例理解/迴圈依賴``

設定三個實體類,StudentA,B,C程式碼如下,A持有B,B持有C,C持有A

public class StudentA {
private StudentB studentB ;

public void setStudentB(StudentB studentB) {
    this.studentB = studentB;
}

public StudentA() {
}

public StudentA(StudentB studentB) {
    this.studentB = studentB;
}

}
當我通過構造器注入時,會產生BeanCurrentlyInCreationException異常。為什麼會出現這種異常,spring如何載入實體?

Spring Bean注入/單例理解/迴圈依賴

Spring容器會將每一個正在建立的Bean 識別符號放在一個“當前建立Bean池”中,Bean識別符號在建立過程中將一直保持在這個池中,因此如果在建立Bean過程中發現自己已經在“當前建立Bean池”裡時將丟擲BeanCurrentlyInCreationException異常表示迴圈依賴;而對於建立完畢的Bean將從“當前建立Bean池”中清除掉。

Spring容器先建立單例StudentA,StudentA依賴StudentB,然後將A放在“當前建立Bean池”中,此時建立StudentB,StudentB依賴StudentC ,然後將B放在“當前建立Bean池”中,此時建立StudentC,StudentC又依賴StudentA, 但是,此時Student已經在池中,所以會報錯,因為在池中的Bean都是未初始化完的,所以會依賴錯誤.

解決這個問題,可以用setter注入的方式。
Spring Bean注入/單例理解/迴圈依賴

Spring是先將Bean物件例項化之後,再設定物件屬性。所以會先呼叫他的無參建構函式例項化。每個物件存在一個map中。當遇到依賴,就去map中呼叫對應的單例物件。
Spring Bean注入/單例理解/迴圈依賴

一部分原始碼
另外: 對於“prototype”作用域Bean,Spring容器無法完成依賴注入,因為“prototype”作用域的Bean,Spring容器不進行快取,因此無法提前暴露一個建立中的Bean。

Spring裝配Bean的過程

例項化;
設定屬性值;
如果實現了BeanNameAware介面,呼叫setBeanName設定Bean的ID或者Name;
如果實現BeanFactoryAware介面,呼叫setBeanFactory 設定BeanFactory;
如果實現ApplicationContextAware,呼叫setApplicationContext設定ApplicationContext
呼叫BeanPostProcessor的預先初始化方法;
呼叫InitializingBean的afterPropertiesSet()方法;
呼叫定製init-method方法;
呼叫BeanPostProcessor的後初始化方法;
Spring容器關閉過程

呼叫DisposableBean的destroy();
呼叫定製的destroy-method方法;

Spring Bean注入/單例理解/迴圈依賴``

瞭解了bean預設是單例模式,不由想spring的單例和設計模式單例同一種嗎?其實不一樣。單例模式是指在一個JVM程序中僅有一個例項,而Spring單例是指一個Spring Bean容器(ApplicationContext)中僅有一個例項。如果有多個Spring容器,可能有多個Bean物件。

spring單例是一種類似登錄檔實現的方式。利用hashmap,向map中註冊和取值,思路類似下面程式碼

public class Singleton {
    private static Map<String,Singleton> map = new HashMap<String,Singleton>();
    static{
        Singleton single = new Singleton();
        map.put(single.getClass().getName(), single);
    }
    //保護的預設構造子
    protected Singleton(){}
    //靜態工廠方法,返還此類惟一的例項
    public static Singleton getInstance(String name) {
        if(name == null) {
            name = Singleton.class.getName();
        }
        if(map.get(name) == null) {
            try {
                map.put(name, (Singleton) Class.forName(name).newInstance());
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
        return map.get(name);
    }
}