接上篇: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。