自訂 Bean 的本質

Spring Framework 提供了許多介面,您可以使用這些介面來自訂 Bean 的本質。本節將它們分組如下

生命週期回呼

為了與容器對 Bean 生命週期的管理互動,您可以實作 Spring InitializingBeanDisposableBean 介面。容器會為前者呼叫 afterPropertiesSet(),為後者呼叫 destroy(),讓 Bean 在初始化和銷毀時執行特定動作。

JSR-250 @PostConstruct@PreDestroy 註解通常被認為是在現代 Spring 應用程式中接收生命週期回呼的最佳實務。使用這些註解表示您的 Bean 不會耦合到 Spring 特定的介面。如需詳細資訊,請參閱使用 @PostConstruct@PreDestroy

如果您不想使用 JSR-250 註解,但仍然想要移除耦合,請考慮 init-methoddestroy-method Bean 定義中繼資料。

在內部,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 組態,您可以使用 @BeaninitMethod 屬性。請參閱接收生命週期回呼。請考慮以下範例

<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。

請注意,@PostConstruct 和初始化方法通常在容器的單例建立鎖定內執行。只有在從 @PostConstruct 方法傳回後,Bean 實例才被視為完全初始化並準備好發布給其他人。此類個別初始化方法僅用於驗證組態狀態,並可能根據給定的組態準備一些資料結構,但不會進一步存取外部 Bean。否則,存在初始化死鎖的風險。

對於需要觸發昂貴的後初始化活動的情況,例如非同步資料庫準備步驟,您的 Bean 應該實作 SmartInitializingSingleton.afterSingletonsInstantiated() 或依賴 Context 重新整理事件:實作 ApplicationListener<ContextRefreshedEvent> 或宣告其註解等效項 @EventListener(ContextRefreshedEvent.class)。這些變體在所有常規單例初始化之後出現,因此在任何單例建立鎖定之外。

或者,您可以實作 (Smart)Lifecycle 介面,並與容器的整體生命週期管理整合,包括自動啟動機制、預先銷毀停止步驟,以及可能的停止/重新啟動回呼(請參閱下文)。

銷毀回呼

實作 org.springframework.beans.factory.DisposableBean 介面讓 Bean 在包含它的容器被銷毀時取得回呼。DisposableBean 介面指定單一方法

void destroy() throws Exception;

我們建議您不要使用 DisposableBean 回呼介面,因為它會不必要地將程式碼耦合到 Spring。或者,我們建議使用@PreDestroy 註解或指定 Bean 定義支援的通用方法。對於基於 XML 的組態中繼資料,您可以使用 <bean/> 上的 destroy-method 屬性。使用 Java 組態,您可以使用 @BeandestroyMethod 屬性。請參閱接收生命週期回呼。請考慮以下定義

<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 也支援銷毀方法的推斷,偵測公用 closeshutdown 方法。這是 Java 組態類別中 @Bean 方法的預設行為,並自動符合 java.lang.AutoCloseablejava.io.Closeable 實作,也不會將銷毀邏輯耦合到 Spring。

對於 XML 的銷毀方法推斷,您可以將 <bean> 元素的 destroy-method 屬性指派為特殊的 (inferred) 值,這會指示 Spring 自動偵測特定 Bean 定義的 Bean 類別上的公用 closeshutdown 方法。您也可以在 <beans> 元素的 default-destroy-method 屬性上設定此特殊的 (inferred) 值,以將此行為套用至整組 Bean 定義(請參閱預設初始化和銷毀方法)。

對於擴充的關閉階段,您可以實作 Lifecycle 介面,並在呼叫任何單例 Bean 的銷毀方法之前接收早期停止訊號。您也可以為時間綁定的停止步驟實作 SmartLifecycle,容器將等待所有此類停止處理完成,然後再繼續執行銷毀方法。

預設初始化和銷毀方法

當您編寫不使用 Spring 特定的 InitializingBeanDisposableBean 回呼介面的初始化和銷毀方法回呼時,您通常會編寫名稱為 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-methoddestroy-method 屬性(在 XML 中)來覆寫預設值。

Spring 容器保證在 Bean 取得所有相依性後立即呼叫已組態的初始化回呼。因此,在原始 Bean 參考上呼叫初始化回呼,這表示 AOP 攔截器等等尚未套用至 Bean。目標 Bean 會先完全建立,然後套用具有其攔截器鏈的 AOP Proxy(例如)。如果目標 Bean 和 Proxy 分別定義,您的程式碼甚至可以與原始目標 Bean 互動,繞過 Proxy。因此,將攔截器套用至 init 方法會不一致,因為這樣做會將目標 Bean 的生命週期耦合到其 Proxy 或攔截器,並在您的程式碼直接與原始目標 Bean 互動時留下奇怪的語意。

組合生命週期機制

從 Spring 2.5 開始,您有三個選項可以控制 Bean 生命週期行為

如果為 Bean 組態了多個生命週期機制,並且每個機制都組態了不同的方法名稱,則每個組態的方法都會在本註解之後列出的順序中執行。但是,如果為這些生命週期機制中的多個機制組態了相同的方法名稱(例如,初始化方法的 init()),則該方法會執行一次,如前一節中所述。

為同一個 Bean 組態的多個生命週期機制,以及不同的初始化方法,會依以下順序呼叫

  1. 使用 @PostConstruct 註解的方法

  2. InitializingBean 回呼介面定義的 afterPropertiesSet()

  3. 自訂組態的 init() 方法

銷毀方法的呼叫順序相同

  1. 使用 @PreDestroy 註解的方法

  2. DisposableBean 回呼介面定義的 destroy()

  3. 自訂組態的 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 重新整理和關閉。

請注意,常規的 org.springframework.context.Lifecycle 介面是明確啟動和停止通知的簡單合約,並不表示在 Context 重新整理時自動啟動。為了精細控制自動啟動以及特定 Bean 的正常停止(包括啟動和停止階段),請考慮改為實作擴充的 org.springframework.context.SmartLifecycle 介面。

此外,請注意,不保證停止通知會在銷毀之前到達。在常規關閉時,所有 Lifecycle Bean 會先收到停止通知,然後再傳播一般銷毀回呼。但是,在 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 的 ApplicationContext 實作已內建程式碼,可在相關的 Web 應用程式關閉時優雅地關閉 Spring IoC 容器。

如果您在非 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 整合涉及執行期可變狀態,例如必須宣告為 volatilerunnable 欄位。雖然常見的生命週期回呼遵循特定順序,例如,保證啟動回呼僅在完全初始化後發生,而停止回呼僅在初始啟動後發生,但常見的停止先於銷毀安排有一個特殊情況:強烈建議任何此類 Bean 中的內部狀態也允許立即銷毀回呼,而無需先前的停止,因為這可能在取消的啟動引導 (bootstrap) 之後的異常關閉期間發生,或者在另一個 Bean 導致停止逾時的情況下發生。

ApplicationContextAwareBeanNameAware

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 參考的另一種替代方案。傳統constructorbyType 自動裝配模式(如 自動裝配協作者 中所述)可以為建構子 (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 介面

除了 ApplicationContextAwareBeanNameAware(在先前討論過),Spring 還提供了廣泛的 Aware 回呼介面,讓 Bean 向容器指示它們需要特定的基礎架構依賴。作為一般規則,名稱指示依賴類型。下表總結了最重要的 Aware 介面

表 1. Aware 介面
名稱 注入的依賴 說明於…

ApplicationContextAware

宣告 ApplicationContext

ApplicationContextAwareBeanNameAware

ApplicationEventPublisherAware

封閉 ApplicationContext 的事件發布者。

ApplicationContext 的其他功能

BeanClassLoaderAware

用於載入 Bean 類別的類別載入器 (class loader)。

實例化 Bean

BeanFactoryAware

宣告 BeanFactory

BeanFactory API

BeanNameAware

宣告 Bean 的名稱。

ApplicationContextAwareBeanNameAware

LoadTimeWeaverAware

為在載入時 (load time) 處理類別定義而定義的編織器 (weaver)。

Spring Framework 中使用 AspectJ 進行載入時編織

MessageSourceAware

用於解析訊息的配置策略(支援參數化和國際化)。

ApplicationContext 的其他功能

NotificationPublisherAware

Spring JMX 通知發布者。

通知

ResourceLoaderAware

用於低階存取資源的配置載入器。

資源

ServletConfigAware

容器在其中運行的當前 ServletConfig。僅在 Web 感知的 Spring ApplicationContext 中有效。

Spring MVC

ServletContextAware

容器在其中運行的當前 ServletContext。僅在 Web 感知的 Spring ApplicationContext 中有效。

Spring MVC

再次注意,使用這些介面會將您的程式碼與 Spring API 綁定,並且不遵循控制反轉風格。因此,我們建議將它們用於需要以程式化方式存取容器的基礎架構 Bean。