定製 Bean 的性質
Spring Framework 提供了許多介面,您可以使用它們來定製 Bean 的性質。本節將它們分組如下
生命週期回撥
為了與容器對 Bean 生命週期的管理進行互動,您可以實現 Spring 的 InitializingBean
和 DisposableBean
介面。容器會為前者呼叫 afterPropertiesSet()
方法,為後者呼叫 destroy()
方法,以便 Bean 在初始化和銷燬時執行特定操作。
在現代 Spring 應用中,JSR-250 的 如果您不想使用 JSR-250 註解但仍想解除耦合,可以考慮使用 |
在內部,Spring Framework 使用 BeanPostProcessor
實現來處理它能找到的任何回撥介面,並呼叫相應的方法。如果您需要 Spring 預設不提供的自定義功能或其他生命週期行為,可以自己實現一個 BeanPostProcessor
。更多資訊請參見容器擴充套件點。
除了初始化和銷燬回撥之外,Spring 管理的物件還可以實現 Lifecycle
介面,以便這些物件能夠參與容器自身生命週期驅動的啟動和停止過程。
本節描述了生命週期回撥介面。
初始化回撥
org.springframework.beans.factory.InitializingBean
介面允許 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 類有這樣一個方法,它將在適當的時候被呼叫。
您也可以類似地配置銷燬方法回撥(即在 XML 中),方法是使用頂級 <beans/>
元素的 default-destroy-method
屬性。
對於現有的 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
介面的擴充套件。它還增加了另外兩個方法,用於響應上下文的重新整理和關閉。
請注意,常規的 另外請注意,停止通知不保證在銷燬之前發生。在常規關機時,所有 |
啟動和關閉呼叫的順序可能很重要。如果任意兩個物件之間存在“depends-on”關係,則依賴方在其依賴項啟動之後啟動,並在其依賴項停止之前停止。然而,有時直接依賴關係是未知的。您可能只知道某種型別的物件應該在另一種型別的物件之前啟動。在這種情況下,SmartLifecycle
介面定義了另一個選項,即其父介面 Phased
上定義的 getPhase()
方法。以下清單顯示了 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
的物件將在啟動時排在最前面,在停止時排在最後面。另一方面,phase 值為 Integer.MAX_VALUE
將表示物件應最後啟動,最先停止(可能是因為它依賴於其他正在執行的程序)。考慮 phase 值時,知道任何未實現 SmartLifecycle
的“正常” Lifecycle
物件的預設 phase 為 0
也很重要。因此,任何負的 phase 值都表示物件應在這些標準組件之前啟動(並在它們之後停止)。任何正的 phase 值則相反。
SmartLifecycle
定義的 stop 方法接受一個回撥。任何實現都必須在該實現的關閉過程完成後呼叫該回調的 run()
方法。這樣可以在必要時實現非同步關閉,因為 LifecycleProcessor
介面的預設實現 DefaultLifecycleProcessor
會在其超時值內等待每個 phase 中的物件組呼叫該回調。每個 phase 的預設超時時間是 30 秒。您可以透過在上下文內定義一個名為 lifecycleProcessor
的 Bean 來覆蓋預設的生命週期處理器例項。如果您只想修改超時時間,定義以下內容即可
<bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor">
<!-- timeout value in milliseconds -->
<property name="timeoutPerShutdownPhase" value="10000"/>
</bean>
如前所述,LifecycleProcessor
介面也定義了用於上下文重新整理和關閉的回撥方法。後者驅動關閉過程,就像顯式呼叫了 stop()
一樣,只是它發生在上下文關閉時。“refresh”回撥則啟用了 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,並且不遵循控制反轉(Inversion of Control)的風格,在該風格中,協作物件作為屬性提供給 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()
或自定義 init 方法之類的初始化回撥之前被呼叫。
其他 Aware
介面
除了 ApplicationContextAware
和 BeanNameAware
(前面討論過)之外,Spring 還提供了廣泛的 Aware
回撥介面,讓 Bean 可以向容器表明它們需要某個基礎架構依賴。通常,名稱表示依賴型別。下表總結了最重要的 Aware
介面
名稱 | 注入的依賴 | 解釋參見… |
---|---|---|
|
宣告 Bean 的 |
|
|
封閉的 |
|
|
用於載入 Bean 類的類載入器。 |
|
|
宣告 Bean 的 |
|
|
宣告 Bean 的名稱。 |
|
|
用於在載入時處理類定義的已定義 weaver。 |
|
|
用於解析訊息的已配置策略(支援引數化和國際化)。 |
|
|
Spring JMX 通知釋出器。 |
|
|
用於低級別訪問資源的已配置載入器。 |
|
|
容器執行時的當前 |
|
|
容器執行時的當前 |
再次注意,使用這些介面會將您的程式碼與 Spring API 繫結,並且不遵循控制反轉的風格。因此,我們建議將它們用於需要以程式設計方式訪問容器的基礎設施 Bean。