自訂 Bean 的本質
Spring Framework 提供了許多介面,您可以使用這些介面來自訂 Bean 的本質。本節將它們分組如下
生命週期回呼
為了與容器對 Bean 生命週期的管理互動,您可以實作 Spring InitializingBean
和 DisposableBean
介面。容器會為前者呼叫 afterPropertiesSet()
,為後者呼叫 destroy()
,讓 Bean 在初始化和銷毀時執行特定動作。
JSR-250 如果您不想使用 JSR-250 註解,但仍然想要移除耦合,請考慮 |
在內部,Spring Framework 使用 BeanPostProcessor
實作來處理它可以找到的任何回呼介面,並呼叫適當的方法。如果您需要自訂功能或其他 Spring 預設未提供的生命週期行為,您可以自行實作 BeanPostProcessor
。如需詳細資訊,請參閱容器擴充點。
除了初始化和銷毀回呼之外,Spring 管理的物件也可以實作 Lifecycle
介面,以便這些物件可以參與啟動和關閉程序,如同容器本身的生命週期所驅動。
本節將說明生命週期回呼介面。
初始化回呼
org.springframework.beans.factory.InitializingBean
介面讓 Bean 在容器設定 Bean 上的所有必要屬性後,執行初始化工作。InitializingBean
介面指定單一方法
void afterPropertiesSet() throws Exception;
我們建議您不要使用 InitializingBean
介面,因為它會不必要地將程式碼耦合到 Spring。或者,我們建議使用@PostConstruct
註解或指定 POJO 初始化方法。對於基於 XML 的組態中繼資料,您可以使用 init-method
屬性來指定具有 void 無引數簽名的方法名稱。使用 Java 組態,您可以使用 @Bean
的 initMethod
屬性。請參閱接收生命週期回呼。請考慮以下範例
<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
-
Java
-
Kotlin
public class ExampleBean {
public void init() {
// do some initialization work
}
}
class ExampleBean {
fun init() {
// do some initialization work
}
}
前面的範例與以下範例(包含兩個清單)具有幾乎完全相同的效果
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
-
Java
-
Kotlin
public class AnotherExampleBean implements InitializingBean {
@Override
public void afterPropertiesSet() {
// do some initialization work
}
}
class AnotherExampleBean : InitializingBean {
override fun afterPropertiesSet() {
// do some initialization work
}
}
但是,前面兩個範例中的第一個範例不會將程式碼耦合到 Spring。
請注意, 對於需要觸發昂貴的後初始化活動的情況,例如非同步資料庫準備步驟,您的 Bean 應該實作 或者,您可以實作 |
銷毀回呼
實作 org.springframework.beans.factory.DisposableBean
介面讓 Bean 在包含它的容器被銷毀時取得回呼。DisposableBean
介面指定單一方法
void destroy() throws Exception;
我們建議您不要使用 DisposableBean
回呼介面,因為它會不必要地將程式碼耦合到 Spring。或者,我們建議使用@PreDestroy
註解或指定 Bean 定義支援的通用方法。對於基於 XML 的組態中繼資料,您可以使用 <bean/>
上的 destroy-method
屬性。使用 Java 組態,您可以使用 @Bean
的 destroyMethod
屬性。請參閱接收生命週期回呼。請考慮以下定義
<bean id="exampleDestructionBean" class="examples.ExampleBean" destroy-method="cleanup"/>
-
Java
-
Kotlin
public class ExampleBean {
public void cleanup() {
// do some destruction work (like releasing pooled connections)
}
}
class ExampleBean {
fun cleanup() {
// do some destruction work (like releasing pooled connections)
}
}
前面的定義與以下定義具有幾乎完全相同的效果
<bean id="exampleDestructionBean" class="examples.AnotherExampleBean"/>
-
Java
-
Kotlin
public class AnotherExampleBean implements DisposableBean {
@Override
public void destroy() {
// do some destruction work (like releasing pooled connections)
}
}
class AnotherExampleBean : DisposableBean {
override fun destroy() {
// do some destruction work (like releasing pooled connections)
}
}
但是,前面兩個定義中的第一個定義不會將程式碼耦合到 Spring。
請注意,Spring 也支援銷毀方法的推斷,偵測公用 close
或 shutdown
方法。這是 Java 組態類別中 @Bean
方法的預設行為,並自動符合 java.lang.AutoCloseable
或 java.io.Closeable
實作,也不會將銷毀邏輯耦合到 Spring。
對於 XML 的銷毀方法推斷,您可以將 <bean> 元素的 destroy-method 屬性指派為特殊的 (inferred) 值,這會指示 Spring 自動偵測特定 Bean 定義的 Bean 類別上的公用 close 或 shutdown 方法。您也可以在 <beans> 元素的 default-destroy-method 屬性上設定此特殊的 (inferred) 值,以將此行為套用至整組 Bean 定義(請參閱預設初始化和銷毀方法)。 |
對於擴充的關閉階段,您可以實作 |
預設初始化和銷毀方法
當您編寫不使用 Spring 特定的 InitializingBean
和 DisposableBean
回呼介面的初始化和銷毀方法回呼時,您通常會編寫名稱為 init()
、initialize()
、dispose()
等的方法。理想情況下,此類生命週期回呼方法的名稱在整個專案中標準化,以便所有開發人員都使用相同的方法名稱並確保一致性。
您可以組態 Spring 容器以「尋找」每個 Bean 上具名的初始化和銷毀回呼方法名稱。這表示作為應用程式開發人員,您可以編寫應用程式類別並使用名為 init()
的初始化回呼,而無需為每個 Bean 定義組態 init-method="init"
屬性。當 Bean 建立時,Spring IoC 容器會呼叫該方法(並根據先前說明的標準生命週期回呼合約)。此功能還強制執行初始化和銷毀方法回呼的一致命名慣例。
假設您的初始化回呼方法名為 init()
,而您的銷毀回呼方法名為 destroy()
。然後您的類別會類似於以下範例中的類別
-
Java
-
Kotlin
public class DefaultBlogService implements BlogService {
private BlogDao blogDao;
public void setBlogDao(BlogDao blogDao) {
this.blogDao = blogDao;
}
// this is (unsurprisingly) the initialization callback method
public void init() {
if (this.blogDao == null) {
throw new IllegalStateException("The [blogDao] property must be set.");
}
}
}
class DefaultBlogService : BlogService {
private var blogDao: BlogDao? = null
// this is (unsurprisingly) the initialization callback method
fun init() {
if (blogDao == null) {
throw IllegalStateException("The [blogDao] property must be set.")
}
}
}
然後,您可以在類似於以下的 Bean 中使用該類別
<beans default-init-method="init">
<bean id="blogService" class="com.something.DefaultBlogService">
<property name="blogDao" ref="blogDao" />
</bean>
</beans>
頂層 <beans/>
元素屬性上 default-init-method
屬性的存在,會導致 Spring IoC 容器將 Bean 類別上名為 init
的方法識別為初始化方法回呼。當建立和組裝 Bean 時,如果 Bean 類別具有這樣的方法,則會在適當的時間調用它。
您可以使用頂層 <beans/>
元素上的 default-destroy-method
屬性,以類似的方式組態銷毀方法回呼(在 XML 中)。
如果現有的 Bean 類別已經具有與慣例不同的回呼方法名稱,您可以透過使用 <bean/>
本身的 init-method
和 destroy-method
屬性(在 XML 中)來覆寫預設值。
Spring 容器保證在 Bean 取得所有相依性後立即呼叫已組態的初始化回呼。因此,在原始 Bean 參考上呼叫初始化回呼,這表示 AOP 攔截器等等尚未套用至 Bean。目標 Bean 會先完全建立,然後套用具有其攔截器鏈的 AOP Proxy(例如)。如果目標 Bean 和 Proxy 分別定義,您的程式碼甚至可以與原始目標 Bean 互動,繞過 Proxy。因此,將攔截器套用至 init
方法會不一致,因為這樣做會將目標 Bean 的生命週期耦合到其 Proxy 或攔截器,並在您的程式碼直接與原始目標 Bean 互動時留下奇怪的語意。
組合生命週期機制
從 Spring 2.5 開始,您有三個選項可以控制 Bean 生命週期行為
-
InitializingBean
和DisposableBean
回呼介面 -
自訂
init()
和destroy()
方法 -
@PostConstruct
和@PreDestroy
註解-
您可以組合這些機制來控制給定的 Bean。
-
如果為 Bean 組態了多個生命週期機制,並且每個機制都組態了不同的方法名稱,則每個組態的方法都會在本註解之後列出的順序中執行。但是,如果為這些生命週期機制中的多個機制組態了相同的方法名稱(例如,初始化方法的 init() ),則該方法會執行一次,如前一節中所述。 |
為同一個 Bean 組態的多個生命週期機制,以及不同的初始化方法,會依以下順序呼叫
-
使用
@PostConstruct
註解的方法 -
由
InitializingBean
回呼介面定義的afterPropertiesSet()
-
自訂組態的
init()
方法
銷毀方法的呼叫順序相同
-
使用
@PreDestroy
註解的方法 -
由
DisposableBean
回呼介面定義的destroy()
-
自訂組態的
destroy()
方法
啟動和關閉回呼
Lifecycle
介面定義了任何具有自身生命週期需求的物件(例如啟動和停止某些背景程序)的基本方法
public interface Lifecycle {
void start();
void stop();
boolean isRunning();
}
任何 Spring 管理的物件都可以實作 Lifecycle
介面。然後,當 ApplicationContext
本身收到啟動和停止訊號時(例如,在執行階段的停止/重新啟動情境中),它會將這些呼叫串聯到該 Context 中定義的所有 Lifecycle
實作。它透過委派給 LifecycleProcessor
來執行此操作,如下清單所示
public interface LifecycleProcessor extends Lifecycle {
void onRefresh();
void onClose();
}
請注意,LifecycleProcessor
本身是 Lifecycle
介面的擴充功能。它還新增了另外兩種方法,用於回應 Context 重新整理和關閉。
請注意,常規的 此外,請注意,不保證停止通知會在銷毀之前到達。在常規關閉時,所有 |
啟動和關閉調用的順序可能很重要。如果任何兩個物件之間存在「depends-on」關係,則相依端會在相依性之後啟動,並在其相依性之前停止。但是,有時,直接相依性是未知的。您可能只知道某種類型的物件應該在另一種類型的物件之前啟動。在這些情況下,SmartLifecycle
介面定義了另一個選項,即 getPhase()
方法,如其超介面 Phased
上所定義。以下清單顯示了 Phased
介面的定義
public interface Phased {
int getPhase();
}
以下清單顯示了 SmartLifecycle
介面的定義
public interface SmartLifecycle extends Lifecycle, Phased {
boolean isAutoStartup();
void stop(Runnable callback);
}
啟動時,階段 (phase) 值最低的物件會優先啟動。停止時,則依相反順序進行。因此,實作 SmartLifecycle
介面且其 getPhase()
方法回傳 Integer.MIN_VALUE
的物件,會是最先啟動和最後停止的物件之一。在光譜的另一端,階段值為 Integer.MAX_VALUE
表示該物件應最後啟動且最先停止(可能是因為它依賴其他程序執行)。在考量階段值時,同樣重要的是要知道,任何未實作 SmartLifecycle
的「一般」Lifecycle
物件的預設階段為 0
。因此,任何負階段值表示物件應在這些標準組件之前啟動(並在它們之後停止)。對於任何正階段值,情況則相反。
SmartLifecycle
定義的 stop 方法接受一個回呼 (callback)。任何實作都必須在該實作的關閉程序完成後,調用該回呼的 run()
方法。這可在必要時啟用非同步關閉,因為 LifecycleProcessor
介面 (DefaultLifecycleProcessor
) 的預設實作,會等待每個階段中的物件群組調用該回呼,直到達到其逾時 (timeout) 值為止。預設的每個階段逾時為 30 秒。您可以透過在容器 (context) 內定義名為 lifecycleProcessor
的 Bean 來覆寫預設的生命週期處理器實例。如果您只想修改逾時,定義以下內容就足夠了
<bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor">
<!-- timeout value in milliseconds -->
<property name="timeoutPerShutdownPhase" value="10000"/>
</bean>
如先前所述,LifecycleProcessor
介面也定義了用於容器 (context) 重新整理和關閉的回呼方法。後者驅動關閉程序,如同明確調用 stop()
一樣,但它發生在容器關閉時。「refresh」回呼則啟用 SmartLifecycle
Bean 的另一個功能。當容器重新整理時(在所有物件都已實例化和初始化之後),會調用該回呼。此時,預設的生命週期處理器會檢查每個 SmartLifecycle
物件的 isAutoStartup()
方法回傳的布林值。如果為 true
,則該物件會在該時點啟動,而不是等待明確調用容器或其自身的 start()
方法(與容器重新整理不同,容器啟動對於標準容器實作不會自動發生)。phase
值和任何「depends-on」依賴關係決定了如先前所述的啟動順序。
在非 Web 應用程式中優雅地關閉 Spring IoC 容器
本節僅適用於非 Web 應用程式。Spring 基於 Web 的 |
如果您在非 Web 應用程式環境(例如,在豐富的用戶端桌面環境中)中使用 Spring 的 IoC 容器,請向 JVM 註冊一個關閉鉤子 (shutdown hook)。這樣做可確保優雅的關閉,並調用單例 Bean (singleton bean) 上的相關銷毀 (destroy) 方法,以便釋放所有資源。您仍然必須正確配置和實作這些銷毀回呼。
若要註冊關閉鉤子,請調用在 ConfigurableApplicationContext
介面上宣告的 registerShutdownHook()
方法,如下列範例所示
-
Java
-
Kotlin
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public final class Boot {
public static void main(final String[] args) throws Exception {
ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
// add a shutdown hook for the above context...
ctx.registerShutdownHook();
// app runs here...
// main method exits, hook is called prior to the app shutting down...
}
}
import org.springframework.context.support.ClassPathXmlApplicationContext
fun main() {
val ctx = ClassPathXmlApplicationContext("beans.xml")
// add a shutdown hook for the above context...
ctx.registerShutdownHook()
// app runs here...
// main method exits, hook is called prior to the app shutting down...
}
執行緒安全性和可見性
Spring 核心容器以執行緒安全的方式發布建立的單例實例,透過單例鎖定來保護存取,並保證在其他執行緒中的可見性。
因此,應用程式提供的 Bean 類別不必擔心其初始化狀態的可見性。常規配置欄位不必標記為 volatile
,只要它們僅在初始化階段期間被修改,即使對於在該初始階段期間可變的基於 Setter 的配置狀態,也提供類似於 final
的可見性保證。如果這些欄位在 Bean 建立階段及其後續的初始發布後被更改,則在每次存取時都需要將它們宣告為 volatile
或由共用鎖定保護。
請注意,在容器端安全初始發布後,並行存取單例 Bean 實例中的此類配置狀態(例如,對於控制器實例或儲存庫實例)是完全執行緒安全的。這包括在一般單例鎖定內處理的常見單例 FactoryBean
實例。
對於銷毀回呼,配置狀態保持執行緒安全,但初始化和銷毀之間累積的任何執行期狀態都應保留在執行緒安全結構中(或對於簡單情況,保留在 volatile
欄位中),如同常見的 Java 指南所述。
如上所示更深入的 Lifecycle
整合涉及執行期可變狀態,例如必須宣告為 volatile
的 runnable
欄位。雖然常見的生命週期回呼遵循特定順序,例如,保證啟動回呼僅在完全初始化後發生,而停止回呼僅在初始啟動後發生,但常見的停止先於銷毀安排有一個特殊情況:強烈建議任何此類 Bean 中的內部狀態也允許立即銷毀回呼,而無需先前的停止,因為這可能在取消的啟動引導 (bootstrap) 之後的異常關閉期間發生,或者在另一個 Bean 導致停止逾時的情況下發生。
ApplicationContextAware
和 BeanNameAware
當 ApplicationContext
建立實作 org.springframework.context.ApplicationContextAware
介面的物件實例時,該實例會被提供對該 ApplicationContext
的參考。以下清單顯示了 ApplicationContextAware
介面的定義
public interface ApplicationContextAware {
void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}
因此,Bean 可以透過 ApplicationContext
介面或將參考轉換為此介面的已知子類別(例如 ConfigurableApplicationContext
,它公開了額外功能),以程式化方式操作建立它們的 ApplicationContext
。一種用途是以程式化方式檢索其他 Bean。有時這種能力很有用。但是,一般而言,您應避免使用它,因為它將程式碼耦合到 Spring,並且不遵循控制反轉 (Inversion of Control) 風格,在控制反轉風格中,協作者 (collaborator) 會作為屬性提供給 Bean。ApplicationContext
的其他方法提供對檔案資源的存取、發布應用程式事件以及存取 MessageSource
。這些額外功能在 ApplicationContext
的其他功能 中描述。
自動裝配 (Autowiring) 是取得對 ApplicationContext
參考的另一種替代方案。傳統的 constructor
和 byType
自動裝配模式(如 自動裝配協作者 中所述)可以為建構子 (constructor) 引數或 Setter 方法參數分別提供 ApplicationContext
類型的依賴。為了獲得更大的彈性,包括自動裝配欄位和多參數方法的能力,請使用基於註解的自動裝配功能。如果您這樣做,如果欄位、建構子或方法攜帶 @Autowired
註解,則 ApplicationContext
會自動裝配到期望 ApplicationContext
類型的欄位、建構子引數或方法參數中。如需更多資訊,請參閱 使用 @Autowired
。
當 ApplicationContext
建立實作 org.springframework.beans.factory.BeanNameAware
介面的類別時,該類別會被提供對其關聯的物件定義中定義的名稱的參考。以下清單顯示了 BeanNameAware 介面的定義
public interface BeanNameAware {
void setBeanName(String name) throws BeansException;
}
回呼在一般 Bean 屬性填充之後但在初始化回呼(例如 InitializingBean.afterPropertiesSet()
或自訂 init-method)之前調用。
其他 Aware
介面
除了 ApplicationContextAware
和 BeanNameAware
(在先前討論過),Spring 還提供了廣泛的 Aware
回呼介面,讓 Bean 向容器指示它們需要特定的基礎架構依賴。作為一般規則,名稱指示依賴類型。下表總結了最重要的 Aware
介面
名稱 | 注入的依賴 | 說明於… |
---|---|---|
|
宣告 |
|
|
封閉 |
|
|
用於載入 Bean 類別的類別載入器 (class loader)。 |
|
|
宣告 |
|
|
宣告 Bean 的名稱。 |
|
|
為在載入時 (load time) 處理類別定義而定義的編織器 (weaver)。 |
|
|
用於解析訊息的配置策略(支援參數化和國際化)。 |
|
|
Spring JMX 通知發布者。 |
|
|
用於低階存取資源的配置載入器。 |
|
|
容器在其中運行的當前 |
|
|
容器在其中運行的當前 |
再次注意,使用這些介面會將您的程式碼與 Spring API 綁定,並且不遵循控制反轉風格。因此,我們建議將它們用於需要以程式化方式存取容器的基礎架構 Bean。