Spring 核心技術(7)
- 2019 年 10 月 3 日
- 筆記
接上篇:Spring 核心技術(6)
version 5.1.8.RELEASE
1.6 訂製 Bean 的特性
Spring Framework 提供了許多可用於自定義 bean 特性的介面。本節將它們分組如下:
1.6.1 生命周期回調
要與容器的 bean 生命周期管理進行交互,可以實現 Spring InitializingBean
和 DisposableBean
介面。容器調用前者 afterPropertiesSet()
和後者的 destroy()
以便讓 bean 在初始化和銷毀 bean 時執行某些操作。
JSR-250
@PostConstruct
和@PreDestroy
註解通常被認為是在現代 Spring 應用程式中接收生命周期回調的最佳實踐。使用這些註解意味著你的 bean 不會耦合到特定的 Spring 介面。有關詳細資訊,請參閱使用 @PostConstruct 和 @PreDestroy。
在內部,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
屬性指定具有無返回值無參數簽名的方法的名稱。使用 Java 配置時,可以使用 @Bean
註解的 initMethod
屬性。請參閱接收生命周期回調。請看以下示例:
<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
public class ExampleBean { public void init() { // do some initialization work } }
前面的示例與以下示例幾乎完全相同(包含兩個列表):
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements InitializingBean { public void afterPropertiesSet() { // do some initialization work } }
但是,上面兩個示例中,第一個示例沒有將程式碼耦合到 Spring。
銷毀回調
實現 org.springframework.beans.factory.DisposableBean
介面允許 bean 在包含它的容器被銷毀時獲得回調。DisposableBean
介面規定了一個方法:
void destroy() throws Exception;
我們建議不要使用 DisposableBean
回調介面,因為並不需要將程式碼耦合到 Spring。此外,我們建議使用 @PreDestroy
註解或指定 bean 定義支援的泛型方法。使用基於 XML 的配置元數據時,可以使用 <bean/>
元素的 destroy-method
屬性。使用 Java 配置時,可以使用 @Bean
的 destroyMethod
屬性。請參閱接收生命周期回調。請看以下定義:
<bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/>
public class ExampleBean { public void cleanup() { // do some destruction work (like releasing pooled connections) } }
前面的定義與以下定義幾乎完全相同:
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements DisposableBean { public void destroy() { // do some destruction work (like releasing pooled connections) } }
但是,上面兩個示例中,第一個示例沒有將程式碼耦合到 Spring。
你可以為
<bean>
元素的destroy-method
屬性指定一個特殊(推測)值,該值指示 Spring 自動檢測特定 bean 類的公共close
或shutdown
方法。(任何實現java.lang.AutoCloseable或java.io.Closeable
的類都可以進行匹配。)你還可以在<beans>
元素的default-destroy-method
屬性上設置此特殊(推測)值,以將此行為應用於整組 bean(請參閱默認初始化和銷毀方法)。請注意,這是使用 Java 配置時的默認行為。
默認初始化和銷毀方法
當你不使用 Spring 特定的 InitializingBean
和 DisposableBean
回調介面編寫初始化和銷毀回調方法時,通常會使用 init()
,initialize()
,dispose()
等來命名方法。理想情況下,此類生命周期回調方法的名稱在項目中是一致的的,以便所有開發人員使用相同的方法名稱並確保一致性。
你可以將Spring容器配置為在每個 bean 上「查找」已經命名的初始化和銷毀回調方法的名稱。這意味著,作為應用程式開發人員,你可以編寫應用程式類並使用 init()
作為初始化回調 ,而無需為每個 bean 定義配置 init-method="init"
屬性。Spring IoC 容器在創建 bean 時調用該方法(根據前面描述的標準生命周期回調)。此功能還強制執行初始化和銷毀方法回調的一致命名約定。
假設你的初始化回調方法已命名為 init()
並且您的銷毀回調方法已命名為 destroy()
。你的類類似於以下示例:
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."); } } }
你可以在類似於以下內容的 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
介面的擴展。它還添加了另外兩種方法來響應刷新和關閉上下文。
請注意,常規的
org.springframework.context.Lifecycle
介面是顯式啟動和停止通知的簡單協議,並不意味著在上下文刷新時自動啟動。要對特定bean的自動啟動(包括啟動階段)進行細粒度控制,請考慮實現org.springframework.context.SmartLifecycle
。
此外,請注意,在銷毀之前不保證能收到停止通知。在常規關閉時,所有Lifecycle
bean 在一般銷毀回調開始之前首先收到停止通知。但是,在上下文生命周期中的熱刷新或中止刷新嘗試時,僅調用銷毀方法。
啟動和關閉調用的順序非常重要。如果任何兩個對象之間存在「依賴」關係,則依賴方在其依賴之後開始,並且在其依賴之前停止。但是,有時,直接依賴性是未知的。你可能只知道某種類型的對象應該在另一種類型的對象之前開始。在這些情況下,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
定義的停止方法接受回調。任何實現必須在該實現的關閉過程完成後調用該回調的 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()
,但它只在上下文關閉時發生。另一方面,’refresh’ 回調啟用了 SmartLifecycle
bean的另一個功能 。刷新上下文時(在實例化並初始化所有對象之後),將調用該回調。此時,默認生命周期處理器檢查每個 SmartLifecycle
對象的 isAutoStartup()
方法返回的布爾值 。如果是 true
,那麼對象是當時就開始的,而不是等待顯式調用上下文或它自己的 start()
方法(與上下文刷新不同,上下文啟動不會自動發生在標準上下文實現中)。phase
值與任何「依賴式」的關係確定了前面所述的啟動順序。
在非 Web 應用程式中優雅地關閉 Spring IoC 容器
本節僅適用於非 Web 應用程式。Spring 的基於 Web 的
ApplicationContext
實現已經具有相關的程式碼,可以在相關 Web 應用程式關閉時正常關閉 Spring IoC 容器。
如果在非 Web 應用程式環境中使用 Spring 的 IoC 容器(例如,在客戶機桌面環境中),請使用 JVM 註冊關閉鉤子。這樣做可確保正常關閉並在單例 bean 上調用相關的銷毀方法,以便釋放所有資源。你必須正確配置和實現這些銷毀回調。
要註冊關閉鉤子,請調用 ConfigurableApplicationContext
介面上聲明的 registerShutdownHook()
方法,如以下示例所示:
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... } }
1.6.2 ApplicationContextAware
和 BeanNameAware
當 ApplicationContext
創建實現 org.springframework.context.ApplicationContextAware
介面的對象實例時,將為 ApplicationContext
提供對該實例的引用。以下清單顯示了 ApplicationContextAware
介面的定義:
public interface ApplicationContextAware { void setApplicationContext(ApplicationContext applicationContext) throws BeansException; }
因此,bean 可以通過 ApplicationContext
介面或通過將引用轉換為此介面的已知子類(例如公開其他功能的 ConfigurableApplicationContext
,)以編程方式操縱創建它們的 ApplicationContext
。一種用途是對其他 bean 進行編程檢索。有時這種功能很有用。但是,一般情況下應該避免使用它,因為協作者作為屬性提供給 bean 時會將程式碼耦合到 Spring 並且不遵循控制反轉風格。ApplicationContext
的其他方法提供對文件資源的訪問,發布應用程式事件以及訪問 MessageSource
。這些附加功能在ApplicationContext
的附加功能中描述 。
從 Spring 2.5 開始,自動裝配是另一種獲取 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
或自定義的初始化方法。
1.6.3 其他 Aware
介面
除了 ApplicationContextAware
和 BeanNameAware
(在之前討論過),Spring 提供了廣泛的 Aware
回調讓 bean 向容器指出他們需要一定的基礎設依賴。作為一般規則,名稱表示依賴關係類型。下表總結了最重要的 Aware
介面:
名稱 | 注入依賴 | 描述 |
---|---|---|
ApplicationContextAware | 聲明 ApplicationContext |
ApplicationContextAware 和 BeanNameAware |
ApplicationEventPublisherAware | 包含 ApplicationContext 的時間發布者 | ApplicationContext 的其他功能 |
BeanClassLoaderAware | 用於載入bean類的類載入器 | 實例化 Bean |
BeanFactoryAware | 聲明 BeanFactory | ApplicationContextAware 和 BeanNameAware |
BeanNameAware | 聲明 bean 的名稱 | ApplicationContextAware 和 BeanNameAware |
BootstrapContextAware | 容器運行的資源適配器 BootstrapContext 。通常僅在支援 JCA 的 ApplicationContext 實例中可用 |
JCA CCI |
LoadTimeWeaverAware | 定義在載入時用於處理類定義的 weaver | 在 Spring 框架中使用 AspectJ 進行載入時織入 |
MessageSourceAware | 用於解析消息的已配置策略(支援參數化和國際化) | ApplicationContext 的其他功能 |
NotificationPublisherAware | Spring JMX 通知發布者 | 通知 |
ResourceLoaderAware | 用於對低層次資源進行訪問的配置載入器 | 資源 |
ServletConfigAware | 當前容器運行的 ServletConfig 。僅在支援 Web 的 ApplicationContext 中有效 |
Spring MVC |
ServletContextAware | 當前容器運行的 ServletContext 。僅在支援 Web 的 ApplicationContext 中有效 |
Spring MVC |
請再次注意,使用這些介面會使你的程式碼與 Spring API 耦合,而且不會遵循 IOC 規範。因此,我們建議將它們用於需要以編程方式訪問容器的基礎架構 bean。