Spring框架參考手冊_5.0.0_中文版_Part II_3.6
文章作者:Tyan
部落格:noahsnail.com | CSDN | 簡書
3.6 定製bean特性
3.6.1 生命週期回撥
為了與容器中bean生命週期的管理進行互動,你可以實現Spring的InitializingBean
和DisposableBean
介面。當初始化beans時容器會呼叫InitializingBean
中的afterPropertiesSet()
方法,當銷燬beans時容器會呼叫DisposableBean
中的destroy()
方法,在這兩個方法中bean可以執行特定的行為。
在現代Spring應用中,通常認為JSR-250的
@PostConstruct
和@PreDestroy
註解是最佳實踐接收生命週期回撥函式的方法。使用這些註解意味著你的bean沒有耦合Spring特定的介面。更多細節請看3.9.8小節,”@PostConstruct和@PreDestroy”。如果你不想使用JSR-250註解,但你仍要注意解耦,可以考慮使用物件定義元資料中的初始化方法和銷燬方法。
在Spring內部,Spring框架使用BeanPostProcessor
實現來處理任何它能發現的回撥介面並呼叫合適的方法。如果你需要定製Spring不能提供的開箱即用的功能或其它生命週期行為,你可以自己實現BeanPostProcessor
。更多資訊請看3.8小節,”容器擴充套件點”。
除了初始化回撥函式和銷燬回撥函式之外,Spring管理的物件也可以實現Lifecycle
介面,這些物件可以參與容器自身生命週期驅動的啟動和關閉過程。
本節描述了生命週期回撥介面。
初始化回撥函式
org.springframework.beans.factory.InitializingBean
介面在容器設定了bean所有的必須屬性之後,允許bean執行初始化工作。InitializingBean
介面指定了一個方法:
void afterPropertiesSet() throws Exception;
建議你不使用InitializingBean
介面,因為它對程式碼與Spring進行了不必要的耦合。作為一種替代方法,你可以使用@PostConstruct
init-method
特性來指定方法的名稱,方法是沒有返回值和引數的。如果使用Java配置,你可以使用@Bean
的initMethod
特性,請看”接收生命週期回撥函式”小節。例如,下面的程式碼:
<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
public class ExampleBean {
public void init() {
// do some initialization work
}
}
等價於:
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程式碼耦合。
預設初始化和銷燬方法
當你編寫初始化回撥函式和析構回撥函式時,不要使用Spring特定的InitializingBean
和DisposableBean
回撥介面,自己編寫方法,方法名通常為init()
,initialize()
,dispose()
等等。理想情況下,這種生命週期回撥方法的名稱在整個工程中是標準化的,以便所有開發人員使用同樣的方法名稱,保證一致性。
你可以配置Spring容器查詢每個bean的初始化方法和析構方法時的名字。這意味著,作為一個應用開發者,你可以編寫應用程式類並使用名為init()
的初始化回撥方法,而不必在每個bean定義中配置init-method="init"
特性。當bean建立時,Spring Ioc容器呼叫這個方法(按照前面描述的標準生命週期回撥約定)。這個功能也強制了初始化方法和析構方法命名規範的一致性。
假設你的初始化回撥方法名為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.");
}
}
}
<beans default-init-method="init">
<bean id="blogService" class="com.foo.DefaultBlogService">
<property name="blogDao" ref="blogDao" />
</bean>
</beans>
位於頂層<beans/>
元素中的default-init-method
特性,會讓Spring IoC容器將beans中的名為init
的方法識別為初始化回撥方法。當一個bean建立和組裝時,如果bean類有這樣一個方法,它會在恰當的時間被呼叫。
在bean被提供了所有依賴之後,Spring容器確保會立刻呼叫配置的初始化回撥方法。因此初始化回撥會在原生bean引用上呼叫,這意味著AOP攔截器等仍不能應用到bean中。首先要完整的建立目標bean,然後才會應用AOP代理(例如)等攔截器鏈。如果分別定義了目標bean和代理,你的程式碼甚至能繞過代理直接與原生的目標bean進行互動。將攔截器應用到初始化方法上可能會產生不一致性,因為這樣做會使目標bean的生命週期與它的代理/攔截器相耦合,當你的程式碼與原生目標bean直接進行互動時,語義會變的很奇怪。
組合生命週期機制
從Spring 2.5開始,在控制bean的生命週期行為時,你有三中選擇:InitializingBean和
DisposableBean回撥介面;定製
init()和
destroy()方法;
@PostConstruct和
@PreDestroy`註解。在控制一個給定bean時你可以組合這些機制。
如果一個bean配置了多生命週期機制,每種機制配置了一個不同的方法名,那麼每一個配置的方法會按照下面的順序列表來執行。但是如果配置了相同的名字——例如,
init()
初始化方法——不止在一個生命週期機制中配置,那麼這個方法只能執行一次,像之前所說的那樣。
同一個bean配置了多生命週期機制,並有不同的初始化方法,那麼呼叫順序如下:
先呼叫有註解
@PostConstruct
的方法然後呼叫
InitializingBean
回撥介面定義的afterPropertiesSet()
方法最好呼叫定製配置的
init()
方法
Destroy methods are called in the same order:
Methods annotated with
@PreDestroy
destroy()
as defined by theDisposableBean
callback interfaceA custom configured
destroy()
method
析構方法按同樣的順序呼叫:
先呼叫有
@PreDestroy
註解的方法再呼叫
DisposableBean
回撥介面定義的destroy()
方法最好呼叫定製配置的
destroy()
方法
啟動和關閉回撥
Lifecycle
介面定義了任何物件生命週期都需要的基本方法(例如啟動和停止一些背景處理):
public interface Lifecycle {
void start();
void stop();
boolean isRunning();
}
任何Spring管理的物件都可以實現那個介面。當ApplicationContext
本身收到啟動啟動和關閉訊號時,例如執行時關閉/再啟動場景,它將級聯呼叫所有的上下文定義的Lifecycle
實現。它通過委託LifecycleProcessor
來完成這個功能:
public interface LifecycleProcessor extends Lifecycle {
void onRefresh();
void onClose();
}
注意LifecycleProcessor
本身是Lifecycle
介面的一個擴充套件。它也添加了兩個其它的方法來響應上下文的再重新整理和關閉的。
注意正規的
org.springframework.context.Lifecycle
介面只是一個顯式啟動/關閉通知的協議,並不意味著在上下文重新整理時自動啟動。考慮實現org.springframework.context.SmartLifecycle
介面來實現對指定bean自動啟動的細粒度控制(包括啟動時期)。請注意停止通知不能保證在銷燬之前到來:在正式關閉時,所有的Lifecycle
beans在通常的析構回撥傳播之前首先會收到停止通知;但是,在上下文使用期間進行熱重新整理或嘗試取消再重新整理,只會呼叫析構方法。
啟動和關閉的呼叫順序是很重要的。如果任何兩個物件間存在一個”depends-on”關係,那麼依賴關係將在它的依賴之後開始,在它的依賴之前停止。然而有時直接的依賴關係是未知的。你可能只知道某個型別的物件應該在另一個型別的物件之前啟動。在那種情況下,SmartLifecycle
介面定義了另一種選擇,也就是說getPhase()
定義在它的父介面Phased
中。
public interface Phased {
int getPhase();
}
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
beans的另一個功能可用。當上下文再重新整理時(所有物件已經例項化並初始化),回撥函式將被呼叫,那時預設的生命週期處理器將會檢查每個SmartLifecycle
物件的isAutoStartup()
方法返回的布林值。如果為true
,物件將會在那時啟動而不是等待上下文的顯式呼叫或它自己的start()
方法(不像上下文再重新整理,對於一個標準的上下文實現上下啟動不會自動發生)。”phase”值以及”depends-on”關係將決定啟動順序,像上面描述的一樣。
在非web應用中妥善的關閉Spring IoC容器
這一節只應用於非web應用。Spring的基於web的
ApplicationContext
實現已經有程式碼來處理當相關的web應用關閉時,妥善關閉Spring IoC容器的問題。
如果你在非web應用環境使用Spring的IoC容器;例如,在一個富桌面客戶端環境中,你在JVM中註冊一個關閉鉤子。這樣做確保了妥善的關閉,為了釋放所有資源需要呼叫與單例beans相關的析構方法。當然,你仍然必須正確的配置和實現這些銷燬回撥函式。
為了註冊一個關閉鉤子,你可以呼叫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(
new String []{"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...
}
}
3.6.2 ApplicationContextAware和BeanNameAware
當ApplicationContext
建立一個實現org.springframework.context.ApplicationContextAware
介面的物件例項時,這個例項會提供一個ApplicationContext
的引用。
public interface ApplicationContextAware {
void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}
因此beans可以以程式設計方式操縱建立它們的ApplicationContext
,通過ApplicationContext
介面,或通過將引用拋給這個介面的一個已知子類,例如ConfigurableApplicationContext
,它暴露了額外的功能。一個方法是程式設計式檢索其他的bean。有時這個能力是很有用的,但是通常你應該避免使用它,因為它耦合了程式碼和Spring,不能遵循控制反轉的風格,在控制反轉中協作者是作為屬性提供給beans的。ApplicationContext
的其它方法提供了對檔案資源的訪問,釋出應用事件,訪問MessageSource
的功能。這些額外的特性將在3.15小節『ApplicationContext”的額外能力』中描述。
從Spring 2.5起,自動裝配是另一種可替代的獲得ApplicationContext
引用的方法。『傳統的』constructor
和byType
自動裝配模式(如3.4.5小節所述,『自動裝配協作者』)可以分別為建構函式引數或setter方法引數提供ApplicationContext
型別的依賴。更多的靈活性包括自動裝配變數的能力和多引數方法,使用新的基於註解的自動裝配特性。如果你這一做的話,ApplicationContext
可以被自動裝配到變數中,建構函式引數中或方法引數中,如果討論的變數,建構函式或方法有@Autowired
註解,那麼可以期望它是ApplicationContext
型別。更多資訊請看3.9.2小節,@autowired
。
當ApplicationContext
建立一個實現了org.springframework.beans.factory.BeanNameAware
介面的類時,類中有相關的物件定義中定義的名稱的引用。
public interface BeanNameAware {
void setBeanName(String name) throws BeansException;
}
在正常的bean屬性填入之後,回撥方法呼叫,但在初始化回撥方法之前,例如InitializingBean
的afterPropertiesSet或一個定製的初始化方法。
3.6.3 其它的Aware介面
除了上面討論的ApplicationContextAware
和BeanNameAware
之外,Spring給予了一系列Aware
介面來允許beans向容器表明它們需要一個確定的基礎結構依賴。最重要的Aware
介面總結如下——作為一個通用規則,名字是依賴型別的一個很好暗示:
表3.4. Aware介面
Name | Injected Dependency | Explained in |
---|---|---|
ApplicationContextAware | 宣告ApplicationContext |
Section 3.6.2, “ApplicationContextAware and BeanNameAware” |
ApplicationEventPublisherAware | 封裝事件釋出的ApplicationContext |
Section 3.15, “Additional Capabilities of the ApplicationContext” |
BeanClassLoaderAware | 用來載入bean的類載入器 | Section 3.3.2, “Instantiating beans” |
BeanFactoryAware | 宣告BeanFactory |
Section 3.6.2, “ApplicationContextAware and BeanNameAware” |
BeanNameAware | 宣告的bean的名字 | Section 3.6.2, “ApplicationContextAware and BeanNameAware” |
BootstrapContextAware | 容器執行的資源自適應BootstrapContext . 通常只在JCA aware ApplicationContexts 可獲得 |
Chapter 28, JCA CCI |
LoadTimeWeaverAware | 載入時為處理類定義定義的weaver | Section 7.8.4, “Load-time weaving with AspectJ in the Spring Framework” |
MessageSourceAware | 解析訊息配置策略 (支援引數化和國際化) | Section 3.15, “Additional Capabilities of the ApplicationContext” |
NotificationPublisherAware | Spring JMX通知釋出器 | Section 27.7, “Notifications” |
ResourceLoaderAware | 為底層訪問資源配置的載入器 | Chapter 4, Resources |
ServletConfigAware | 容器執行的當前ServletConfig 。 僅在web感知的Spring ApplicationContext 中有效 |
Chapter 18, Web MVC framework |
ServletContextAware | 容器執行的當前ServletContext 。 僅在web感知的Spring ApplicationContext 中有效 |
Chapter 18, Web MVC framework |
注意這些介面的用法將你的程式碼與Spring進行了捆綁,不符合控制反轉的風格。因此,它們是為那麼需要以程式設計方式訪問容器的基礎結構beans推薦的。