文章作者:Tyan
博客:noahsnail.com | CSDN | 簡書
3.6 定制bean特性
3.6.1 生命周期回調(diào)
為了與容器中bean生命周期的管理進(jìn)行交互魔眨,你可以實(shí)現(xiàn)Spring的InitializingBean
和DisposableBean
接口力试。當(dāng)初始化beans時(shí)容器會(huì)調(diào)用InitializingBean
中的afterPropertiesSet()
方法跋核,當(dāng)銷毀beans時(shí)容器會(huì)調(diào)用DisposableBean
中的destroy()
方法绞惦,在這兩個(gè)方法中bean可以執(zhí)行特定的行為。
在現(xiàn)代Spring應(yīng)用中爽哎,通常認(rèn)為JSR-250的
@PostConstruct
和@PreDestroy
注解是最佳實(shí)踐接收生命周期回調(diào)函數(shù)的方法姐帚。使用這些注解意味著你的bean沒有耦合Spring特定的接口。更多細(xì)節(jié)請(qǐng)看3.9.8小節(jié)铺根,"@PostConstruct和@PreDestroy"宪躯。
如果你不想使用JSR-250注解,但你仍要注意解耦夷都,可以考慮使用對(duì)象定義元數(shù)據(jù)中的初始化方法和銷毀方法眷唉。
在Spring內(nèi)部,Spring框架使用BeanPostProcessor
實(shí)現(xiàn)來處理任何它能發(fā)現(xiàn)的回調(diào)接口并調(diào)用合適的方法囤官。如果你需要定制Spring不能提供的開箱即用的功能或其它生命周期行為冬阳,你可以自己實(shí)現(xiàn)BeanPostProcessor
。更多信息請(qǐng)看3.8小節(jié)党饮,"容器擴(kuò)展點(diǎn)"肝陪。
除了初始化回調(diào)函數(shù)和銷毀回調(diào)函數(shù)之外,Spring管理的對(duì)象也可以實(shí)現(xiàn)Lifecycle
接口刑顺,這些對(duì)象可以參與容器自身生命周期驅(qū)動(dòng)的啟動(dòng)和關(guān)閉過程氯窍。
本節(jié)描述了生命周期回調(diào)接口。
初始化回調(diào)函數(shù)
org.springframework.beans.factory.InitializingBean
接口在容器設(shè)置了bean所有的必須屬性之后蹲堂,允許bean執(zhí)行初始化工作狼讨。InitializingBean
接口指定了一個(gè)方法:
void afterPropertiesSet() throws Exception;
建議你不使用InitializingBean
接口,因?yàn)樗鼘?duì)代碼與Spring進(jìn)行了不必要的耦合柒竞。作為一種替代方法政供,你可以使用@PostConstruct
注解或指定一個(gè)POPJO的初始化方法。在基于XML配置元數(shù)據(jù)的情況下,你可以使用init-method
特性來指定方法的名稱布隔,方法是沒有返回值和參數(shù)的离陶。如果使用Java配置,你可以使用@Bean
的initMethod
特性衅檀,請(qǐng)看"接收生命周期回調(diào)函數(shù)"小節(jié)招刨。例如,下面的代碼:
<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
public class ExampleBean {
public void init() {
// do some initialization work
}
}
等價(jià)于:
public class AnotherExampleBean implements InitializingBean {
public void afterPropertiesSet() {
// do some initialization work
}
}
但沒有與Spring代碼耦合哀军。
銷毀回調(diào)函數(shù)
實(shí)現(xiàn)org.springframework.beans.factory.DisposableBean
接口允許容器包含的bean銷毀時(shí)調(diào)用回調(diào)函數(shù)沉眶。DisposableBean
接口指定了一個(gè)方法:
void destroy() throws Exception;
建議你不使用DisposableBean
回調(diào)接口,因?yàn)樗鼘?duì)代碼與Spring進(jìn)行了不必要的耦合排苍。作為一種替代方法沦寂,你可以使用@PreDestroy
注解或指定一個(gè)bean定義支持的通用方法。在基于XML配置元數(shù)據(jù)的情況下淘衙,你可以使用<bean/>
的destroy-method
特性传藏。如果使用Java配置,你可以使用@Bean
的destroyMethod
特性彤守,請(qǐng)看"接收生命周期回調(diào)"小節(jié)毯侦。例如,下面的定義:
<bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/>
public class ExampleBean {
public void cleanup() {
// do some destruction work (like releasing pooled connections)
}
}
等價(jià)于:
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements DisposableBean {
public void destroy() {
// do some destruction work (like releasing pooled connections)
}
}
但沒有與Spring代碼耦合具垫。
默認(rèn)初始化和銷毀方法
當(dāng)你編寫初始化回調(diào)函數(shù)和析構(gòu)回調(diào)函數(shù)時(shí)侈离,不要使用Spring特定的InitializingBean
和DisposableBean
回調(diào)接口,自己編寫方法筝蚕,方法名通常為init()
卦碾,initialize()
,dispose()
等等起宽。理想情況下洲胖,這種生命周期回調(diào)方法的名稱在整個(gè)工程中是標(biāo)準(zhǔn)化的,以便所有開發(fā)人員使用同樣的方法名稱坯沪,保證一致性绿映。
你可以配置Spring容器查找每個(gè)bean的初始化方法和析構(gòu)方法時(shí)的名字。這意味著腐晾,作為一個(gè)應(yīng)用開發(fā)者叉弦,你可以編寫應(yīng)用程序類并使用名為init()
的初始化回調(diào)方法,而不必在每個(gè)bean定義中配置init-method="init"
特性藻糖。當(dāng)bean創(chuàng)建時(shí)淹冰,Spring Ioc容器調(diào)用這個(gè)方法(按照前面描述的標(biāo)準(zhǔn)生命周期回調(diào)約定)。這個(gè)功能也強(qiáng)制了初始化方法和析構(gòu)方法命名規(guī)范的一致性巨柒。
假設(shè)你的初始化回調(diào)方法名為init()
樱拴,析構(gòu)回調(diào)方法名為destroy()
凝颇。你的類應(yīng)該與下面例子中的類類似。
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
特性疹鳄,會(huì)讓Spring IoC容器將beans中的名為init
的方法識(shí)別為初始化回調(diào)方法。當(dāng)一個(gè)bean創(chuàng)建和組裝時(shí)芦岂,如果bean類有這樣一個(gè)方法瘪弓,它會(huì)在恰當(dāng)?shù)臅r(shí)間被調(diào)用。
在bean被提供了所有依賴之后禽最,Spring容器確保會(huì)立刻調(diào)用配置的初始化回調(diào)方法腺怯。因此初始化回調(diào)會(huì)在原生bean引用上調(diào)用,這意味著AOP攔截器等仍不能應(yīng)用到bean中川无。首先要完整的創(chuàng)建目標(biāo)bean呛占,然后才會(huì)應(yīng)用AOP代理(例如)等攔截器鏈。如果分別定義了目標(biāo)bean和代理懦趋,你的代碼甚至能繞過代理直接與原生的目標(biāo)bean進(jìn)行交互晾虑。將攔截器應(yīng)用到初始化方法上可能會(huì)產(chǎn)生不一致性,因?yàn)檫@樣做會(huì)使目標(biāo)bean的生命周期與它的代理/攔截器相耦合仅叫,當(dāng)你的代碼與原生目標(biāo)bean直接進(jìn)行交互時(shí)帜篇,語義會(huì)變的很奇怪。
組合生命周期機(jī)制
從Spring 2.5開始诫咱,在控制bean的生命周期行為時(shí)笙隙,你有三中選擇:InitializingBean和
DisposableBean回調(diào)接口;定制
init()和
destroy()方法坎缭;
@PostConstruct和
@PreDestroy`注解竟痰。在控制一個(gè)給定bean時(shí)你可以組合這些機(jī)制。
如果一個(gè)bean配置了多生命周期機(jī)制掏呼,每種機(jī)制配置了一個(gè)不同的方法名坏快,那么每一個(gè)配置的方法會(huì)按照下面的順序列表來執(zhí)行。但是如果配置了相同的名字——例如哄尔,
init()
初始化方法——不止在一個(gè)生命周期機(jī)制中配置假消,那么這個(gè)方法只能執(zhí)行一次,像之前所說的那樣岭接。
同一個(gè)bean配置了多生命周期機(jī)制富拗,并有不同的初始化方法,那么調(diào)用順序如下:
先調(diào)用有注解
@PostConstruct
的方法然后調(diào)用
InitializingBean
回調(diào)接口定義的afterPropertiesSet()
方法最好調(diào)用定制配置的
init()
方法
Destroy methods are called in the same order:
Methods annotated with
@PreDestroy
destroy()
as defined by theDisposableBean
callback interfaceA custom configured
destroy()
method
析構(gòu)方法按同樣的順序調(diào)用:
先調(diào)用有
@PreDestroy
注解的方法再調(diào)用
DisposableBean
回調(diào)接口定義的destroy()
方法最好調(diào)用定制配置的
destroy()
方法
啟動(dòng)和關(guān)閉回調(diào)
Lifecycle
接口定義了任何對(duì)象生命周期都需要的基本方法(例如啟動(dòng)和停止一些背景處理):
public interface Lifecycle {
void start();
void stop();
boolean isRunning();
}
任何Spring管理的對(duì)象都可以實(shí)現(xiàn)那個(gè)接口鸣戴。當(dāng)ApplicationContext
本身收到啟動(dòng)啟動(dòng)和關(guān)閉信號(hào)時(shí)啃沪,例如運(yùn)行時(shí)關(guān)閉/再啟動(dòng)場景,它將級(jí)聯(lián)調(diào)用所有的上下文定義的Lifecycle
實(shí)現(xiàn)窄锅。它通過委托LifecycleProcessor
來完成這個(gè)功能:
public interface LifecycleProcessor extends Lifecycle {
void onRefresh();
void onClose();
}
注意LifecycleProcessor
本身是Lifecycle
接口的一個(gè)擴(kuò)展创千。它也添加了兩個(gè)其它的方法來響應(yīng)上下文的再刷新和關(guān)閉的缰雇。
注意正規(guī)的
org.springframework.context.Lifecycle
接口只是一個(gè)顯式啟動(dòng)/關(guān)閉通知的協(xié)議,并不意味著在上下文刷新時(shí)自動(dòng)啟動(dòng)追驴⌒涤矗考慮實(shí)現(xiàn)org.springframework.context.SmartLifecycle
接口來實(shí)現(xiàn)對(duì)指定bean自動(dòng)啟動(dòng)的細(xì)粒度控制(包括啟動(dòng)時(shí)期)。請(qǐng)注意停止通知不能保證在銷毀之前到來:在正式關(guān)閉時(shí)殿雪,所有的Lifecycle
beans在通常的析構(gòu)回調(diào)傳播之前首先會(huì)收到停止通知暇咆;但是,在上下文使用期間進(jìn)行熱刷新或嘗試取消再刷新丙曙,只會(huì)調(diào)用析構(gòu)方法爸业。
啟動(dòng)和關(guān)閉的調(diào)用順序是很重要的。如果任何兩個(gè)對(duì)象間存在一個(gè)"depends-on"關(guān)系亏镰,那么依賴關(guān)系將在它的依賴之后開始扯旷,在它的依賴之前停止。然而有時(shí)直接的依賴關(guān)系是未知的索抓。你可能只知道某個(gè)類型的對(duì)象應(yīng)該在另一個(gè)類型的對(duì)象之前啟動(dòng)钧忽。在那種情況下,SmartLifecycle
接口定義了另一種選擇纸兔,也就是說getPhase()
定義在它的父接口Phased
中惰瓜。
public interface Phased {
int getPhase();
}
public interface SmartLifecycle extends Lifecycle, Phased {
boolean isAutoStartup();
void stop(Runnable callback);
}
當(dāng)開始時(shí),最低相位的對(duì)象先啟動(dòng)汉矿,當(dāng)停止時(shí)崎坊,最高相位的對(duì)象先停止。因此洲拇,實(shí)現(xiàn)了SmartLifecycle
接口奈揍,getPhase()
方法返回值為Integer.MIN_VALUE
的對(duì)象將最先啟動(dòng)并最后停止。另一方面赋续,相位值Integer.MAX_VALUE
表明對(duì)象應(yīng)該最后啟動(dòng)男翰,最先停止(可能是因?yàn)樗蕾嚻渌\(yùn)行的進(jìn)程)。當(dāng)考慮相位值時(shí)纽乱,知道任何沒有實(shí)現(xiàn)SmartLifecycle
接口的Lifecycle
對(duì)象的默認(rèn)值為0是很重要的蛾绎。因此,任何負(fù)相位值表示對(duì)象應(yīng)該在那么標(biāo)準(zhǔn)組件之前啟動(dòng)(在它們之后停止)鸦列,反之為任何正相位值租冠。
正如你看到的,在SmartLifecycle
中定義的停止方法接收一個(gè)回調(diào)函數(shù)薯嗤。任何實(shí)現(xiàn)在關(guān)閉進(jìn)程完成之后都必須調(diào)用回調(diào)的run()
方法顽爹。當(dāng)需要時(shí)這可以進(jìn)行異步關(guān)閉,因?yàn)?code>LifecycleProcessor接口骆姐、DefaultLifecycleProcessor
接口的默認(rèn)實(shí)現(xiàn)會(huì)等待每個(gè)階段的對(duì)象組直到達(dá)到超時(shí)值镜粤,然后調(diào)用回調(diào)函數(shù)捏题。默認(rèn)每個(gè)階段的超時(shí)值為30秒。你可以在上下文中通過定義名為"lifecycleProcessor"的bean來覆蓋默認(rèn)的生命周期處理器實(shí)例肉渴。如果你只想修改超時(shí)值公荧,如下定義是足夠的:
<bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor">
<!-- timeout value in milliseconds -->
<property name="timeoutPerShutdownPhase" value="10000"/>
</bean>
像上面提到的那樣,LifecycleProcessor
接口為再刷新和上下文的關(guān)閉也定義了回調(diào)方法同规。后者會(huì)簡單的驅(qū)動(dòng)關(guān)閉進(jìn)程就像顯式的調(diào)用了stop()
方法一樣稚矿,但當(dāng)上下文關(guān)閉時(shí)它才會(huì)發(fā)生。另一方面refresh
回調(diào)能使SmartLifecycle
beans的另一個(gè)功能可用捻浦。當(dāng)上下文再刷新時(shí)(所有對(duì)象已經(jīng)實(shí)例化并初始化),回調(diào)函數(shù)將被調(diào)用桥爽,那時(shí)默認(rèn)的生命周期處理器將會(huì)檢查每個(gè)SmartLifecycle
對(duì)象的isAutoStartup()
方法返回的布爾值朱灿。如果為true
,對(duì)象將會(huì)在那時(shí)啟動(dòng)而不是等待上下文的顯式調(diào)用或它自己的start()
方法(不像上下文再刷新钠四,對(duì)于一個(gè)標(biāo)準(zhǔn)的上下文實(shí)現(xiàn)上下啟動(dòng)不會(huì)自動(dòng)發(fā)生)盗扒。"phase"值以及"depends-on"關(guān)系將決定啟動(dòng)順序,像上面描述的一樣缀去。
在非web應(yīng)用中妥善的關(guān)閉Spring IoC容器
這一節(jié)只應(yīng)用于非web應(yīng)用侣灶。Spring的基于web的
ApplicationContext
實(shí)現(xiàn)已經(jīng)有代碼來處理當(dāng)相關(guān)的web應(yīng)用關(guān)閉時(shí),妥善關(guān)閉Spring IoC容器的問題缕碎。
如果你在非web應(yīng)用環(huán)境使用Spring的IoC容器褥影;例如,在一個(gè)富桌面客戶端環(huán)境中咏雌,你在JVM中注冊(cè)一個(gè)關(guān)閉鉤子凡怎。這樣做確保了妥善的關(guān)閉,為了釋放所有資源需要調(diào)用與單例beans相關(guān)的析構(gòu)方法赊抖。當(dāng)然统倒,你仍然必須正確的配置和實(shí)現(xiàn)這些銷毀回調(diào)函數(shù)。
為了注冊(cè)一個(gè)關(guān)閉鉤子氛雪,你可以調(diào)用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
當(dāng)ApplicationContext
創(chuàng)建一個(gè)實(shí)現(xiàn)org.springframework.context.ApplicationContextAware
接口的對(duì)象實(shí)例時(shí)房匆,這個(gè)實(shí)例會(huì)提供一個(gè)ApplicationContext
的引用。
public interface ApplicationContextAware {
void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}
因此beans可以以編程方式操縱創(chuàng)建它們的ApplicationContext
报亩,通過ApplicationContext
接口浴鸿,或通過將引用拋給這個(gè)接口的一個(gè)已知子類,例如ConfigurableApplicationContext
捆昏,它暴露了額外的功能赚楚。一個(gè)方法是編程式檢索其他的bean。有時(shí)這個(gè)能力是很有用的骗卜,但是通常你應(yīng)該避免使用它宠页,因?yàn)樗詈狭舜a和Spring左胞,不能遵循控制反轉(zhuǎn)的風(fēng)格,在控制反轉(zhuǎn)中協(xié)作者是作為屬性提供給beans的举户。ApplicationContext
的其它方法提供了對(duì)文件資源的訪問烤宙,發(fā)布應(yīng)用事件,訪問MessageSource
的功能俭嘁。這些額外的特性將在3.15小節(jié)『ApplicationContext”的額外能力』中描述躺枕。
從Spring 2.5起,自動(dòng)裝配是另一種可替代的獲得ApplicationContext
引用的方法供填」赵疲『傳統(tǒng)的』constructor
和byType
自動(dòng)裝配模式(如3.4.5小節(jié)所述,『自動(dòng)裝配協(xié)作者』)可以分別為構(gòu)造函數(shù)參數(shù)或setter方法參數(shù)提供ApplicationContext
類型的依賴近她。更多的靈活性包括自動(dòng)裝配變量的能力和多參數(shù)方法叉瘩,使用新的基于注解的自動(dòng)裝配特性。如果你這一做的話粘捎,ApplicationContext
可以被自動(dòng)裝配到變量中薇缅,構(gòu)造函數(shù)參數(shù)中或方法參數(shù)中,如果討論的變量攒磨,構(gòu)造函數(shù)或方法有@Autowired
注解泳桦,那么可以期望它是ApplicationContext
類型。更多信息請(qǐng)看3.9.2小節(jié)娩缰,@autowired
灸撰。
當(dāng)ApplicationContext
創(chuàng)建一個(gè)實(shí)現(xiàn)了org.springframework.beans.factory.BeanNameAware
接口的類時(shí),類中有相關(guān)的對(duì)象定義中定義的名稱的引用拼坎。
public interface BeanNameAware {
void setBeanName(String name) throws BeansException;
}
在正常的bean屬性填入之后梧奢,回調(diào)方法調(diào)用,但在初始化回調(diào)方法之前演痒,例如InitializingBean
的afterPropertiesSet或一個(gè)定制的初始化方法亲轨。
3.6.3 其它的Aware接口
除了上面討論的ApplicationContextAware
和BeanNameAware
之外,Spring給予了一系列Aware
接口來允許beans向容器表明它們需要一個(gè)確定的基礎(chǔ)結(jié)構(gòu)依賴鸟顺。最重要的Aware
接口總結(jié)如下——作為一個(gè)通用規(guī)則惦蚊,名字是依賴類型的一個(gè)很好暗示:
表3.4. Aware接口
Name | Injected Dependency | Explained in |
---|---|---|
ApplicationContextAware | 聲明ApplicationContext
|
Section 3.6.2, “ApplicationContextAware and BeanNameAware” |
ApplicationEventPublisherAware | 封裝事件發(fā)布的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 | 容器運(yùn)行的資源自適應(yīng)BootstrapContext . 通常只在JCA aware ApplicationContexts 可獲得 |
Chapter 28, JCA CCI |
LoadTimeWeaverAware | 加載時(shí)為處理類定義定義的weaver | Section 7.8.4, “Load-time weaving with AspectJ in the Spring Framework” |
MessageSourceAware | 解析消息配置策略 (支持參數(shù)化和國際化) | Section 3.15, “Additional Capabilities of the ApplicationContext” |
NotificationPublisherAware | Spring JMX通知發(fā)布器 | Section 27.7, “Notifications” |
ResourceLoaderAware | 為底層訪問資源配置的加載器 | Chapter 4, Resources |
ServletConfigAware | 容器運(yùn)行的當(dāng)前ServletConfig 。 僅在web感知的Spring ApplicationContext 中有效 |
Chapter 18, Web MVC framework |
ServletContextAware | 容器運(yùn)行的當(dāng)前ServletContext 讯嫂。 僅在web感知的Spring ApplicationContext 中有效 |
Chapter 18, Web MVC framework |
注意這些接口的用法將你的代碼與Spring進(jìn)行了捆綁蹦锋,不符合控制反轉(zhuǎn)的風(fēng)格。因此欧芽,它們是為那么需要以編程方式訪問容器的基礎(chǔ)結(jié)構(gòu)beans推薦的莉掂。