Spring核心——Bean的定義與控制

爛豬皮發表於2018-06-28

在前面兩篇介紹Sring核心與設計模式的文章中,分別介紹了Ioc容器Bean的依賴關係。如果閱讀過前2文就會知道,Spring的整個運轉機制就是圍繞著IoC容器以及Bean展開的。IoC就是一個籃子,所有的Bean都向裡面扔。除了提供籃子功能建立並存放Bean之外,IoC還要負責管理Bean與Bean之間的關係——依賴注入。之前也提到Bean是Spring核心容器的最小工作單元,Spring一些更高階的功能(例如切面、代理)都是在Bean的基礎上實現。

除了管理Bean與Bean之間的關係,IoC還提供了對Bean自身進行控制的各項功能,本文將介紹Bean的生命週期功能以及狀態定義功能。

前置依賴

Bean與Bean之間存在依賴關係,可以是強依賴(通過XML和註解直接宣告依賴)、也可以是弱依賴(ApplicationContextAware等方式獲取)。當一個Bean需要另外一個Bean完成初始化後自身才能工作時,例如一個Bean依賴DataSoruce,但是DataSource的初始化需要較長時間。這個時候用

depends-on
宣告前置依賴即可:

<!-- 依賴多個Bean使用,號分割 -->
<bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao">
    <property name="manager" ref="manager" />
</bean>

<bean id="manager" class="ManagerBean" />
<bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />複製程式碼

延遲載入

通常情況下,所有的 singleton 型別的Bean都會在容器建立後進行初始化,簡單的說就是啟動Jvm就開始建立(實際上是建立ApplicationContext的某個實現類例項之後)。

IoC支援所有的 singleton Bean在使用時再載入,這樣做的好處是可以大大節省初始化的時間。但是如果你的應用對啟動時間的長短並不敏感,建議讓所有的 singleton 都啟動時載入。這樣可以在啟動時就發現一些問題,而不是在執行很久直到使用時才由使用者去觸發這個問題。或者可以根據場景來使用決定是否延遲,例如開發時使用延遲載入,而在整合測試或上生產時關閉。

可以設定全域性延遲載入,也可以設定某個Bean延遲載入:

<beans default-lazy-init="true">
    <!-- 所有的Bean知道使用的時候才會進行載入... -->
</beans>複製程式碼
<!-- 只有lazy類延遲載入 -->
<bean id="lazy" class="com.foo.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.foo.AnotherBean"/>複製程式碼

需要注意的是,在設定某個單獨的Bean延遲載入時,如果有某個沒有延遲載入的Bean要依賴他,那實際上也會在初始化的時候就載入。

還要強調一下,這裡的“載入”僅僅是為了表示一個類被Ioc創造並放置容器中,和classLoad方法將class檔案中的位元組碼載入到方法區的載入是兩個概念。

延遲載入在設計模式上是單例模式一種延伸,通常也被稱為懶漢模式。單例通常有雙重鎖+volatile、靜態類和列舉三種方式實現。在Effective

一書中對三種模式都有深入的解析。而對於Spring容器而言,列舉的方式肯定不好用了,靜態類由於屬於自身程式碼級別應該也不會用,所以雙重鎖的實現方式較為可信。不過我沒去看過原始碼,僅屬於猜測。

生命週期方法

一個Bean的建立、使用再到最後銷燬稱為"Bean的生命週期"。Spring框架為Bean的生命週期各個階段提供了多種回掉方法來處理各種狀態或者資料。

初始化方法

當一個Bean完成初始化並注入各項引數之後,初始化回掉方法會被呼叫,簡單的說就是完成建立之後會被呼叫。實現初始化回撥方法有2個路徑:1.繼承org.springframework.beans.factory.InitializingBean介面,然後實現 afterPropertiesSet方法。2.在Bean的XML配置上使用init-method屬性來制定要呼叫的初始化:

繼承實現:

<bean id="a" class="x.y.A" />複製程式碼
package x.y;
public class A implements InitializingBean {
    public void afterPropertiesSet(){
        // init
    }
}複製程式碼

配置實現:

<bean id="a" class="x.y.A" init-method="init" />複製程式碼
package x.y;
public class A {
    public void init(){}
}複製程式碼

2種方法都等效,實際使用是我們應該使用哪一種方法呢?

InitializingBean是Spring早期實現的一個生命週期回撥方法。但是在JCP推出JSR-250和JSR-330規範之後,Spring的大神們開始意識到基於超程式設計思想和配置手段來實現非侵入式框架(Not Coupled)才是正道。所以現在都是推薦使用配置檔案和JSR-250的@PostConstruct(關於各種Annotation的使用請關注後續的文章)。現在依然保留InitializingBean應該是考慮到相容問題。

銷燬方法

與建立方法相對應的是銷燬方法。當一個類將要被銷燬之前,對應的銷燬回撥方法會被呼叫。銷燬方法也有一個繼承實現和配置+註解實現:

繼承實現:

<bean id="a" class="x.y.A" />複製程式碼
package x.y;
public class A implements DisposableBean {
    public void destroy(){
        // 銷燬資源
    }
}複製程式碼

配置實現:

<bean id="a" class="x.y.A" destroy-method="cleanUp" />複製程式碼
package x.y;
public class A {
    public void cleanUp(){
        // 銷燬資源
    }
}複製程式碼

依然建議銷燬手段也使用配置或@PreDestroy來設定銷燬方法。

全域性配置初始化與銷燬方法

IoC容器還提供了全域性配置初始化與銷燬方法的配置:

package x.y;
public class A {
    public void init(){
        // 初始化資源
    }
    public void destroy(){
        // 銷燬資源
    }
}複製程式碼
<beans default-init-method="init" default-destroy-method="destroy">
     <bean id="a" class="x.y.A"/>
     <!-- bean configuration -->
</beans>複製程式碼

通過在<beans>標籤上使用

default-init-method
default-destroy-method
屬性引數,可以為容器中所有的Bean統一指定初始化和銷燬的生命週期方法。

如果在<beans>上設定2個預設的生命週期方法,同時在<bean>上也指定了

init-method
destroy-method,
回撥方法會以<bean>上的配置為準。這樣就保證全域性配置與單獨配置可以共存。

使用初始化或銷燬2個生命週期方法注意的要點:

  1. 初始化和銷燬都提供了3種手段:XML配置、註解、以及實現介面。系統的各個部分會交由不同的團隊開發,不遵循統一的規範,建議使用滿足JSR規範的註解——@PostConstruct、@PreDestroy。如果是統一的團隊,準訓一致的規範,建議使用<beans>的屬性統一名稱使用全域性配置。
  2. 如果Bean設計到代理模式時(例如使用了AOP),那麼生命週期方法被呼叫時,有可能代理類還沒有被建立出來。因為生命週期方法是實體類完成對應工作之後就會被呼叫,而與代理類無關。


相關文章