定製 Bean 的性質

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

生命週期回撥

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

在現代 Spring 應用中,JSR-250 的 @PostConstruct@PreDestroy 註解通常被認為是接收生命週期回撥的最佳實踐。使用這些註解意味著您的 Bean 不會與 Spring 特定的介面耦合。詳細資訊請參見使用 @PostConstruct@PreDestroy

如果您不想使用 JSR-250 註解但仍想解除耦合,可以考慮使用 init-methoddestroy-method Bean 定義元資料。

在內部,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 配置,您可以使用 @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() 或依賴上下文重新整理事件:實現 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 類有這樣一個方法,它將在適當的時候被呼叫。

您也可以類似地配置銷燬方法回撥(即在 XML 中),方法是使用頂級 <beans/> 元素的 default-destroy-method 屬性。

對於現有的 Bean 類已經存在與約定命名不符的回撥方法的情況,您可以透過(即在 XML 中)使用 <bean/> 自身的 init-methoddestroy-method 屬性指定方法名來覆蓋預設設定。

Spring 容器保證,配置的初始化回撥會在 Bean 獲得所有依賴項後立即被呼叫。因此,初始化回撥是在原始 Bean 引用上呼叫的,這意味著 AOP 攔截器等尚未應用於該 Bean。目標 Bean 首先被完整建立,然後應用帶有其攔截器鏈的 AOP 代理(例如)。如果目標 Bean 和代理是分開定義的,您的程式碼甚至可以直接與原始目標 Bean 互動,繞過代理。因此,將攔截器應用於 init 方法將是不一致的,因為這樣做會將目標 Bean 的生命週期與其代理或攔截器耦合,並且當您的程式碼直接與原始目標 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 本身收到啟動和停止訊號時(例如,執行時停止/重新啟動場景),它會將這些呼叫級聯到在該上下文內定義的所有 Lifecycle 實現。它透過委託給 LifecycleProcessor 來實現這一點,如下面的列表中所示

public interface LifecycleProcessor extends Lifecycle {

	void onRefresh();

	void onClose();
}

請注意,LifecycleProcessor 本身就是 Lifecycle 介面的擴充套件。它還增加了另外兩個方法,用於響應上下文的重新整理和關閉。

請注意,常規的 org.springframework.context.Lifecycle 介面是一個用於顯式啟動和停止通知的普通契約,它並不意味著在上下文重新整理時自動啟動。為了對自動啟動進行細粒度控制,並實現特定 Bean 的優雅停止(包括啟動和停止階段),請考慮實現擴充套件的 org.springframework.context.SmartLifecycle 介面。

另外請注意,停止通知不保證在銷燬之前發生。在常規關機時,所有 Lifecycle Bean 首先收到停止通知,然後才傳播一般的銷燬回撥。但是,在上下文生命週期內的熱重新整理或停止的重新整理嘗試期間,只調用銷燬方法。

啟動和關閉呼叫的順序可能很重要。如果任意兩個物件之間存在“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 的 ApplicationContext 實現已經內建了程式碼,以便在相關的 Web 應用關閉時優雅地關閉 Spring IoC 容器。

如果您在非 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 整合涉及執行時可變狀態,例如必須宣告為 volatilerunnable 欄位。雖然常見的生命週期回撥遵循特定順序,例如,啟動回撥保證只在完全初始化後發生,停止回撥保證只在初始啟動後發生,但存在一個特殊情況,即常見的停止在銷燬之前的安排:強烈建議任何此類 Bean 中的內部狀態也允許在沒有先行停止的情況下進行即時銷燬回撥,因為這可能發生在取消引導後的異常關機期間或由另一個 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)的風格,在該風格中,協作物件作為屬性提供給 Bean。ApplicationContext 的其他方法提供了檔案資源訪問、釋出應用程式事件以及訪問 MessageSource 的能力。這些附加特性在ApplicationContext 的附加功能中進行了描述。

自動裝配是獲取對 ApplicationContext 引用的另一種替代方法。傳統constructorbyType 自動裝配模式(如自動裝配協作物件中所述)可以分別為建構函式引數或 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 介面

除了 ApplicationContextAwareBeanNameAware(前面討論過)之外,Spring 還提供了廣泛的 Aware 回撥介面,讓 Bean 可以向容器表明它們需要某個基礎架構依賴。通常,名稱表示依賴型別。下表總結了最重要的 Aware 介面

表 1. Aware 介面
名稱 注入的依賴 解釋參見…​

ApplicationContextAware

宣告 Bean 的 ApplicationContext

ApplicationContextAwareBeanNameAware

ApplicationEventPublisherAware

封閉的 ApplicationContext 的事件釋出器。

ApplicationContext 的附加功能

BeanClassLoaderAware

用於載入 Bean 類的類載入器。

例項化 Bean

BeanFactoryAware

宣告 Bean 的 BeanFactory

BeanFactory API

BeanNameAware

宣告 Bean 的名稱。

ApplicationContextAwareBeanNameAware

LoadTimeWeaverAware

用於在載入時處理類定義的已定義 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。