自定義 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 方法。您還可以將此特殊的 (inferred) 值設定到 <beans> 元素的 default-destroy-method 屬性上,以將此行為應用於整個 bean 定義集(參見 預設初始化和銷燬方法)。 |
|
對於擴充套件的關閉階段,您可以實現 |
預設初始化和銷燬方法
當您編寫不使用 Spring 特定 InitializingBean 和 DisposableBean 回撥介面的初始化和銷燬方法回撥時,通常會編寫名為 init()、initialize()、dispose() 等的方法。理想情況下,這些生命週期回撥方法的名稱在專案中是標準化的,以便所有開發人員使用相同的方法名稱並確保一致性。
您可以將 Spring 容器配置為在每個 Bean 上“查詢”指定的初始化和銷燬回撥方法名稱。這意味著您作為應用程式開發人員可以編寫應用程式類並使用名為 init() 的初始化回撥,而無需為每個 Bean 定義配置 init-method="init" 屬性。Spring IoC 容器在 Bean 建立時(並根據前面描述的標準生命週期回撥契約)呼叫該方法。此功能還強制執行初始化和銷燬方法回撥的一致命名約定。
假設您的初始化回撥方法名為 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 類已經有與約定不符的命名回撥方法,您可以透過指定(在 XML 中)方法名,使用 <bean/> 本身的 init-method 和 destroy-method 屬性來覆蓋預設值。
Spring 容器保證在 Bean 獲得所有依賴項後立即呼叫配置的初始化回撥。因此,初始化回撥是在原始 Bean 引用上呼叫的,這意味著 AOP 攔截器等尚未應用於 Bean。目標 Bean 首先完全建立,然後應用 AOP 代理(例如)及其攔截器鏈。如果目標 Bean 和代理是分開定義的,您的程式碼甚至可以直接與原始目標 Bean 互動,繞過代理。因此,將攔截器應用於 init 方法將是不一致的,因為這樣做會將目標 Bean 的生命週期與其代理或攔截器耦合,並在您的程式碼直接與原始目標 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 自身接收到啟動和停止訊號時(例如,在執行時停止/重新啟動場景中),它會將這些呼叫級聯到該上下文中定義的所有 Lifecycle 實現。它透過委託給 LifecycleProcessor 來完成此操作,如以下列表所示:
public interface LifecycleProcessor extends Lifecycle {
void onRefresh();
void onClose();
}
請注意,LifecycleProcessor 本身是 Lifecycle 介面的一個擴充套件。它還添加了另外兩個方法,用於響應上下文的重新整理和關閉。
|
請注意,常規的 此外,請注意,停止通知不保證在銷燬之前發生。在正常關閉時,所有 |
啟動和關閉呼叫的順序可能很重要。如果任意兩個物件之間存在“依賴於”關係,則依賴方在其依賴項之後啟動,並在其依賴項之前停止。然而,有時直接依賴項是未知的。您可能只知道某種型別的物件應該在另一種型別的物件之前啟動。在這些情況下,SmartLifecycle 介面定義了另一個選項,即在其超介面 Phased 上定義的 getPhase() 方法。以下列表顯示了 Phased 介面的定義:
public interface Phased {
int getPhase();
}
以下列表顯示了 SmartLifecycle 介面的定義:
public interface SmartLifecycle extends Lifecycle, Phased {
boolean isAutoStartup();
void stop(Runnable callback);
}
啟動時,相位值最低的物件首先啟動。停止時,遵循相反的順序。因此,實現 SmartLifecycle 且其 getPhase() 方法返回 Integer.MIN_VALUE 的物件將是首批啟動和最後停止的物件之一。另一方面,相位值為 Integer.MAX_VALUE 將表示該物件應最後啟動和最先停止(可能是因為它依賴於其他程序的執行)。在考慮相位值時,瞭解不實現 SmartLifecycle 的任何“正常” Lifecycle 物件的預設相位為 0 也很重要。因此,任何負相位值表示物件應在這些標準組件之前啟動(並在它們之後停止)。對於任何正相位值,情況則相反。
SmartLifecycle 定義的 stop 方法接受一個回撥。任何實現都必須在該實現的關閉過程完成後呼叫該回調的 run() 方法。這在必要時支援非同步關閉,因為 LifecycleProcessor 介面的預設實現 DefaultLifecycleProcessor 會在每個階段的物件組呼叫該回調之前等待其超時值。每個階段的預設超時時間為 30 秒。您可以透過在上下文中定義一個名為 lifecycleProcessor 的 Bean 來覆蓋預設的生命週期處理器例項。如果您只想修改超時時間,定義以下內容即可:
<bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor">
<!-- timeout value in milliseconds -->
<property name="timeoutPerShutdownPhase" value="10000"/>
</bean>
如前所述,LifecycleProcessor 介面還定義了上下文重新整理和關閉的回撥方法。後者驅動關閉過程,就像顯式呼叫了 stop() 一樣,但它發生在上下文關閉時。另一方面,“重新整理”回撥啟用了 SmartLifecycle Bean 的另一個功能。當上下文重新整理時(所有物件都已例項化和初始化後),將呼叫該回調。此時,預設的生命週期處理器會檢查每個 SmartLifecycle 物件的 isAutoStartup() 方法返回的布林值。如果為 true,則該物件會在此時啟動,而不是等待對上下文或其自身的 start() 方法的顯式呼叫(與上下文重新整理不同,上下文啟動不會自動發生對於標準上下文實現)。phase 值和任何“depends-on”關係決定了啟動順序,如前所述。
在非 Web 應用程式中優雅地關閉 Spring IoC 容器
|
本節僅適用於非 Web 應用程式。Spring 基於 Web 的 |
如果您在非 Web 應用程式環境(例如,富客戶端桌面環境)中使用 Spring 的 IoC 容器,請向 JVM 註冊一個關閉鉤子。這樣做可以確保優雅關閉並呼叫單例 Bean 上的相關銷燬方法,從而釋放所有資源。您仍然必須正確配置和實現這些銷燬回撥。
要註冊關閉鉤子,請呼叫 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 例項。
對於銷燬回撥,配置狀態保持執行緒安全,但初始化和銷燬之間累積的任何執行時狀態都應按照通用 Java 準則儲存線上程安全結構中(或在簡單情況下儲存在 volatile 欄位中)。
如上所示,更深層次的 Lifecycle 整合涉及執行時可變狀態,例如必須宣告為 volatile 的 runnable 欄位。雖然常見的生命週期回撥遵循一定的順序,例如,啟動回撥保證僅在完全初始化後發生,停止回撥僅在初始啟動後發生,但存在一個特殊情況,即在銷燬之前進行常見的停止安排:強烈建議任何此類 Bean 中的內部狀態也允許在沒有先行停止的情況下立即進行銷燬回撥,因為這可能發生在取消引導後的異常關閉期間或由於另一個 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 耦合,並且不遵循控制反轉的風格,在該風格中,協作者以屬性的形式提供給 Bean。ApplicationContext 的其他方法提供對檔案資源、釋出應用程式事件和訪問 MessageSource 的訪問。這些附加功能在ApplicationContext 的附加功能中描述。
自動裝配是獲取 ApplicationContext 引用的另一種替代方法。傳統的 constructor 和 byType 自動裝配模式(如 自動裝配協作者 中所述)可以分別為建構函式引數或 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() 或自定義初始化方法等初始化回撥之前呼叫。
其他 Aware 介面
除了 ApplicationContextAware 和 BeanNameAware(前面討論過)之外,Spring 還提供了各種 Aware 回撥介面,讓 Bean 可以向容器指示它們需要某個基礎設施依賴項。一般來說,名稱表示依賴項型別。下表總結了最重要的 Aware 介面:
| 名稱 | 注入依賴項 | 解釋於… |
|---|---|---|
|
宣告 |
|
|
封閉 |
|
|
用於載入 Bean 類的類載入器。 |
|
|
宣告 |
|
|
宣告 Bean 的名稱。 |
|
|
在載入時處理類定義的已定義織入器。 |
|
|
配置用於解析訊息的策略(支援引數化和國際化)。 |
|
|
Spring JMX 通知釋出器。 |
|
|
配置用於低階資源訪問的載入器。 |
|
|
容器當前執行的 |
|
|
容器當前執行的 |
再次注意,使用這些介面會將您的程式碼與 Spring API 繫結,並且不遵循控制反轉的風格。因此,我們建議將它們用於需要以程式設計方式訪問容器的基礎設施 Bean。