前文已經(jīng)描述了Bean的作用域,本文將描述Bean的一些生命周期作用朱嘴,配置還有Bean的繼承。
定制Bean
生命周期回調(diào)
開發(fā)者通過實現(xiàn)Spring的InitializeingBean
和DisposableBean
接口,就可以讓容器來管理Bean的生命周期。容器會調(diào)用afterPropertiesSet()
前和destroy()
后才會允許Bean在初始化和銷毀Bean的時候執(zhí)行一些操作浸遗。
JSR-250的
@PostConstruct
和@PreDestroy
注解就是現(xiàn)代Spring應(yīng)用生命周期回調(diào)的最佳實踐。使用這些注解意味著Bean不在耦合在Spring特定的接口上箱亿。詳細(xì)內(nèi)容跛锌,后續(xù)將會介紹。
如果開發(fā)者不想使用JSR-250的注解届惋,仍然可以考慮使用init-method
和destroy-method
定義來解耦髓帽。
內(nèi)部來說,Spring框架使用BeanPostProcessor
的實現(xiàn)來處理任何接口的回調(diào)脑豹,BeanPostProcessor
能夠找到并調(diào)用合適的方法郑藏。如果開發(fā)者需要一些Spring并不直接提供的生命周期行為,開發(fā)者可以自行實現(xiàn)一個BeanPostProcessor
瘩欺。更多的信息可以參考后面的容器擴(kuò)展點必盖。
除了初始化和銷毀回調(diào),Spring管理的對象也實現(xiàn)了Lifecycle
接口來讓管理的對象在容器的生命周期內(nèi)啟動和關(guān)閉俱饿。
生命周期回調(diào)在本節(jié)會進(jìn)行詳細(xì)描述歌粥。
初始化回調(diào)
org.springframework.beans.factory.InitializingBean
接口允許Bean在所有的必要的依賴配置配置完成后來執(zhí)行初始化Bean的操作。InitializingBean
接口中特指了一個方法:
void afterPropertiesSet() throws Exception;
Spring團(tuán)隊建議開發(fā)者不要使用InitializingBean
接口拍埠,因為這樣會不必要的將代碼耦合到Spring之上失驶。而通過使用@PostConstruct
注解或者指定一個POJO的實現(xiàn)方法,比實現(xiàn)接口要更好枣购。在基于XML的配置元數(shù)據(jù)上嬉探,開發(fā)者可以使用init-method
屬性來指定一個沒有參數(shù)的方法。使用Java配置的開發(fā)者可以使用@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的涩堤。
銷毀回調(diào)
實現(xiàn)了org.springframework.beans.factory.DisposableBean
接口的Bean就能通讓容器通過回調(diào)來銷毀Bean所用的資源。DisposableBean
接口包含了一個方法:
void destroy() throws Exception;
同InitializingBean同樣分瘾,Spring團(tuán)隊仍然不建議開發(fā)者來使用DisposableBean
回調(diào)接口定躏,因為這樣會將開發(fā)者的代碼耦合到Spring代碼上。換種方式,比如使用@PreDestroy
注解或者指定一個Bean支持的配置方法痊远,比如在基于XML的配置元數(shù)據(jù)中垮抗,開發(fā)者可以在Bean標(biāo)簽上指定destroy-method
屬性。而在Java配置中碧聪,開發(fā)者可以配置@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/>
標(biāo)簽的destroy-method
可以被配置為特殊指定的值逞姿,來方便讓Spring能夠自動的檢查到close
或者shutdown
方法(可以實現(xiàn)java.lang.AutoCloseable
或者java.io.Closeable
都會匹配辞嗡。)這個特殊指定的值可以配置到<beans/>
的default-destroy-method
來讓所有的Bean實現(xiàn)這個行為。
默認(rèn)初始化和銷毀方法
當(dāng)開發(fā)者不使用Spring特有的InitializingBean
和DisposableBean
回調(diào)接口來實現(xiàn)初始化和銷毀方法的時候滞造,開發(fā)者通常定義的方法名字都是好似init()
续室,initialize()
或者是dispose()
等等。那么谒养,想這類的方法就可以標(biāo)準(zhǔn)化挺狰,來讓所有的開發(fā)者都使用一樣的名字來確保一致性。
開發(fā)者可以配置Spring容器來針對每一個Bean都查找這種名字的初始化和銷毀回調(diào)函數(shù)买窟。也就是說丰泊,任何的一個應(yīng)用開發(fā)者,都會在應(yīng)用的類中使用一個叫init()
的初始化回調(diào)始绍,而不需要在每個Bean中定義init-method="init"
這中屬性瞳购。Spring IoC容器會在Bean創(chuàng)建的時候調(diào)用那個方法(就如前面描述的標(biāo)準(zhǔn)生命周期一樣。)這個特性也強(qiáng)制開發(fā)者為其他的初始化以及銷毀回調(diào)函數(shù)使用同樣的名字亏推。
假設(shè)開發(fā)者的初始化回調(diào)方法名字為init()
而銷毀的回調(diào)方法為destroy()
学赛。那么開發(fā)者的類就會好似如下的代碼:
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/>
標(biāo)簽上面的default-init-method
屬性會讓Spring IoC容器識別叫做init
的方法來作為Bean的初始化回調(diào)方法。當(dāng)Bean創(chuàng)建和裝載之后吞杭,如果Bean有這么一個方法的話盏浇,Spring容器就會在合適的時候調(diào)用。
類似的篇亭,開發(fā)者也可以配置默認(rèn)銷毀回調(diào)函數(shù)缠捌,基于XML的配置就在<beans/>
標(biāo)簽上面使用default-destroy-method
屬性锄贷。
當(dāng)存在一些Bean的類有了一些回調(diào)函數(shù)译蒂,而和配置的默認(rèn)回調(diào)函數(shù)不同的時候,開發(fā)者可以通過特指的方式來覆蓋掉默認(rèn)的回調(diào)函數(shù)谊却。以XML為例柔昼,就是通過使用<bean>
標(biāo)簽的init-method
和destroy-method
來覆蓋掉<beans/>
中的配置。
Spring容器會做出如下保證炎辨,Bean會在裝載了所有的依賴以后捕透,立刻就開始執(zhí)行初始化回調(diào)。這樣的話,初始化回調(diào)只會在直接的Bean引用裝載好后調(diào)用乙嘀,而AOP攔截器還沒有應(yīng)用到Bean上末购。首先目標(biāo)Bean會完全初始化好,然后虎谢,AOP代理以及其攔截鏈才能應(yīng)用盟榴。如果目標(biāo)Bean以及代理是分開定義的,那么開發(fā)者的代碼甚至可以跳過AOP而直接和引用的Bean交互婴噩。因此擎场,在初始化方法中應(yīng)用攔截器會前后矛盾,因為這樣做耦合了目標(biāo)Bean的生命周期和代理/攔截器几莽,還會因為同Bean直接交互而產(chǎn)生奇怪的現(xiàn)象迅办。
聯(lián)合生命周期機(jī)制
在Spring 2.5之后,開發(fā)者有三種選擇來控制Bean的生命周期行為:
-
InitializingBean
和DisposableBean
回調(diào)接口 - 自定義的
init()
以及destroy
方法 - 使用
@PostConstruct
以及@PreDestroy
注解
開發(fā)者也可以在Bean上聯(lián)合這些機(jī)制一起使用
如果Bean配置了多個生命周期機(jī)制章蚣,而且每個機(jī)制配置了不同的方法名字站欺,那么每個配置的方法會按照后面描述的順序來執(zhí)行。然而究驴,如果配置了相同的名字镊绪,比如說初始化回調(diào)為
init()
,在不止一個生命周期機(jī)制配置為這個方法的情況下洒忧,這個方法只會執(zhí)行一次蝴韭。
如果一個Bean配置了多個生命周期機(jī)制,并且含有不同的方法名熙侍,執(zhí)行的順序如下:
- 包含
@PostConstruct
注解的方法 - 在
InitializingBean
接口中的afterPropertiesSet()
方法 - 自定義的
init()
方法
銷毀方法的執(zhí)行順序和初始化的執(zhí)行順序相同:
- 包含
@PreDestroy
注解的方法 - 在
DisposableBean
接口中的destroy()
方法 - 自定義的
destroy()
方法
啟動和關(guān)閉回調(diào)
Lifecycle
接口中為任何有自己生命周期需求的對象定義了基本的方法(比如啟動和停止一些后臺進(jìn)程):
public interface Lifecycle {
void start();
void stop();
boolean isRunning();
}
任何Spring管理的對象都可實現(xiàn)上面的接口榄鉴。那么當(dāng)ApplicationContext
本身受到了啟動或者停止的信號時,ApplicationContext
會通過委托LifecycleProcessor
來串聯(lián)上下文中的Lifecycle
的實現(xiàn)蛉抓。
public interface LifecycleProcessor extends Lifecycle {
void onRefresh();
void onClose();
}
從上面代碼我們可以發(fā)現(xiàn)LifecycleProcessor
是Lifecycle
接口的擴(kuò)展庆尘。LifecycleProcessor
增加了另外的兩個方法來針對上下文的刷新和關(guān)閉做出反應(yīng)。
常規(guī)的
org.springframework.context.Lifecycle
接口只是為明確的開始/停止通知提供一個契約巷送,而并不表示在上下文刷新時自動開始驶忌。考慮實現(xiàn)org.springframework.context.SmartLifecycle
接口可以取代在某個Bean的自動啟動過程(包括啟動階段)中的細(xì)粒度控制笑跛。同時付魔,停止通知并不能保證在銷毀之前出現(xiàn):在正常的關(guān)閉情況下,所有的Lifecycle
Bean都會在銷毀回調(diào)準(zhǔn)備好之前收到停止停止飞蹂,然而几苍,在上下文存活期的熱刷新或者停止刷新嘗試的時候,只會調(diào)用銷毀方法陈哑。
啟動和關(guān)閉調(diào)用是很重要的妻坝。如果不同的Bean之間存在depends-on
的關(guān)系的話伸眶,被依賴的一方需要更早的啟動,而且關(guān)閉的更早刽宪。然而厘贼,有的時候直接的依賴是未知的,而開發(fā)者僅僅知道哪一種類型需要更早進(jìn)行初始化圣拄。在這種情況下涂臣,SmartLifecycle
接口定義了另一種選項,就是其父接口Phased
中的getPhase()
方法售担。
public interface Phased {
int getPhase();
}
public interface SmartLifecycle extends Lifecycle, Phased {
boolean isAutoStartup();
void stop(Runnable callback);
}
當(dāng)啟動時赁遗,擁有最低的phased
的對象優(yōu)先啟動,而當(dāng)關(guān)閉時族铆,是相反的順序岩四。因此,如果一個對象實現(xiàn)了SmartLifecycle
然后令其getPhase()
方法返回了Integer.MIN_VALUE
的話哥攘,就會讓該對象最早啟動剖煌,而最晚銷毀。顯然逝淹,如果getPhase()
方法返回了Integer.MAX_VALUE
就說明了該對象會最晚啟動耕姊,而最早銷毀。當(dāng)考慮到使用phased
的值得時候栅葡,也同時需要了解正常沒有實現(xiàn)SmartLifecycle
的Lifecycle
對象的默認(rèn)值茉兰,這個值為0。因此欣簇,任何負(fù)值將標(biāo)兵對象會在標(biāo)準(zhǔn)組件啟動之前啟動规脸,在標(biāo)準(zhǔn)組件銷毀以后再進(jìn)行銷毀。
SmartLifecycle
接口也定義了一個stop
的回調(diào)函數(shù)熊咽。任何實現(xiàn)了SmartLifecycle
接口的函數(shù)都必須在關(guān)閉流程完成之后調(diào)用回調(diào)中的run()
方法莫鸭。這樣做可以是能異步關(guān)閉。而LifecycleProcessor
的默認(rèn)實現(xiàn)DefaultLifecycleProcessor
會等到配置的超時時間之后再調(diào)用回調(diào)横殴。默認(rèn)的每一階段的超時時間為30秒被因。開發(fā)者可以通過定義一個叫做lifecycleProcessor
的Bean來覆蓋默認(rèn)的生命周期處理器。如果開發(fā)者需要配置超時時間衫仑,可以通過如下代碼:
<bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor">
<!-- timeout value in milliseconds -->
<property name="timeoutPerShutdownPhase" value="10000"/>
</bean>
和前文提到的梨与,LifecycleProcessor
接口定義了回調(diào)方法來刷新和關(guān)閉山下文。關(guān)閉的話惑畴,如果stop()
方法已經(jīng)明確調(diào)用了蛋欣,那么就會驅(qū)動關(guān)閉的流程航徙,但是如果是上下文關(guān)閉就不會發(fā)生這種情況如贷。而刷新的回調(diào)會使能SmartLifecycle
的另一個特性陷虎。當(dāng)上下文刷新完畢(所有的對象已經(jīng)實例化并初始化),那么就會調(diào)用回調(diào)杠袱,默認(rèn)的生命周期處理器會檢查每一個SmartLifecycle
對象的isAutoStartup()
返回的Bool值尚猿。如果為真,對象將會自動啟動而不是等待明確的上下文調(diào)用楣富,或者調(diào)用自己的start()
方法(不同于上下文刷新凿掂,標(biāo)準(zhǔn)的上下文實現(xiàn)是不會自動啟動的)。phased
的值以及depends-on
關(guān)系會決定對象啟動和銷毀的順序纹蝴。
在非Web應(yīng)用關(guān)閉Spring IoC容器
這一部分只是針對于非Web的應(yīng)用庄萎。Spring的基于web的
ApplicationContext
實現(xiàn)已經(jīng)有代碼在web應(yīng)用關(guān)閉的時候能夠優(yōu)雅的關(guān)閉Spring IoC容器。
如果開發(fā)者在非web應(yīng)用環(huán)境使用Spring IoC容器的話塘安, 比如糠涛,在桌面客戶端的環(huán)境下,開發(fā)者需要在JVM上注冊一個關(guān)閉的鉤子兼犯。來確保在關(guān)閉Spring IoC容器的時候能夠調(diào)用相關(guān)的銷毀方法來釋放掉對應(yīng)的資源忍捡。當(dāng)然,開發(fā)者也必須要正確的配置和實現(xiàn)那些銷毀回調(diào)切黔。
開發(fā)者可以在ConfigurableApplicationContext
接口調(diào)用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...
}
}
ApplicationContextAware
和BeanNameAware
當(dāng)ApplicationContext
在創(chuàng)建實現(xiàn)了org.springframework.context.ApplicationContextAware
接口的對象時砸脊,該對象的實例會包含一個到ApplicationContext
的引用。
public interface ApplicationContextAware {
void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}
這樣Bean就能夠通過編程的方式操作和創(chuàng)建ApplicationContext
了纬霞。通過ApplicationContext
接口凌埂,或者通過將引用轉(zhuǎn)換成已知的接口的子類,比如ConfigurableApplicationContext
就能夠顯式一些額外的功能诗芜。其中的一個用法就是可以通過編程的方式來獲取其他的Bean侨舆。有的時候這個能力很有用,然而绢陌,Spring團(tuán)隊推薦最好避免這樣做挨下,因為這樣會耦合代碼到Spring上,同時也沒有遵循IoC的風(fēng)格脐湾。其他的ApplicationContext
的方法可以提供一些到資源的訪問臭笆,發(fā)布應(yīng)用事件,或者進(jìn)入MessageSource
秤掌。這些信息在后續(xù)的針對ApplicationContext
的描述中會講到愁铺。
在Spring 2.5版本,自動裝載也是獲得ApplicationContext
的一種方式闻鉴。傳統(tǒng)的構(gòu)造函數(shù)和通過類型的裝載方式(前文Spring核心技術(shù)IoC容器(四)有相關(guān)描述)可以通過構(gòu)造函數(shù)或者是setter方法的方式注入茵乱。開發(fā)者也可以通過注解注入的方式使用更多的特性。
當(dāng)ApplicationContext
創(chuàng)建了一個實現(xiàn)了org.springframework.beans.factory.BeanNameAware
接口的類孟岛,那么這個類就可以針對其名字進(jìn)行配置瓶竭。
public interface BeanNameAware {
void setBeanName(string name) throws BeansException;
}
這個回調(diào)的調(diào)用處于屬性配置完以后督勺,但是初始化回調(diào)之前,比如InitializingBean
的afterPropertiesSet()
方法以及自定義的初始化方法等斤贰。
其他Aware
接口
除了上面描述的兩種Aware接口智哀,Spring還提供了一系列的Aware
接口來讓Bean告訴容器,這些Bean需要一些具體的基礎(chǔ)設(shè)施信息荧恍。最重要的一些Aware
接口都在下面表中進(jìn)行了描述:
名字 | 注入的依賴 |
---|---|
ApplicationContextAware |
聲明的ApplicationContext
|
ApplicationEventPlulisherAware |
ApplicationContext 中的事件發(fā)布器 |
BeanClassLoaderAware |
加載Bean使用的類加載器 |
BeanFactoryAware |
聲明的BeanFactory
|
BeanNameAware |
Bean的名字 |
BootstrapContextAware |
容器運行的資源適配器BootstrapContext 瓷叫,通常僅在JCA環(huán)境下有效 |
LoadTimeWeaverAware |
加載期間處理類定義的weaver |
MessageSourceAware |
解析消息的配置策略 |
NotificationPublisherAware |
Spring JMX通知發(fā)布器 |
PortletConfigAware |
容器當(dāng)前運行的PortletConfig ,僅在web下的Spring ApplicationContext 中可見 |
PortletContextAware |
容器當(dāng)前運行的PortletContext 送巡,僅在web下的Spring ApplicationContext 中可見 |
ResourceLoaderAware |
配置的資源加載器 |
ServletConfigAware |
容器當(dāng)前運行的ServletConfig 摹菠,僅在web下的Spring ApplicationContext 中可見 |
ServletContextAware |
容器當(dāng)前運行的ServletContext ,僅在web下的Spring ApplicationContext 中可見 |
再次的聲明骗爆,上面這些接口的使用時違反IoC原則的辨嗽,除非必要,最好不要使用淮腾。
Bean繼承
Bean的定義可以包括很多的配置信息糟需,包括構(gòu)造參數(shù),屬性等等谷朝,也可以包括一些容器指定的信息洲押,比如初始化方法,工廠方法等等圆凰。子Bean會繼承父Bean的配置信息杈帐。子Bean也可以覆蓋父Bean的一些值,或者增加一些值专钉。通過定義父Bean和子Bean可以減少配置內(nèi)容挑童,是一種高效的模板性能。
如果開發(fā)者通過編程的方式跟ApplicationContext
交流跃须,就會知道子Bean是通過ChildBeanDefinition
類表示的站叼。大多數(shù)的開發(fā)者不需要再這個級別上來跟子Bean定義交互,只需要在ClassPathXmlApplicationContext
中顯式的配置Bean就可以了菇民。當(dāng)使用基于XML的元數(shù)據(jù)配置的時候尽楔,開發(fā)者通過使用parent
屬性來定義子Bean,如下所示:
<bean id="inheritedTestBean" abstract="true"
class="org.springframework.beans.TestBean">
<property name="name" value="parent"/>
<property name="age" value="1"/>
</bean>
<bean id="inheritsWithDifferentClass"
class="org.springframework.beans.DerivedTestBean"
parent="inheritedTestBean" init-method="initialize">
<property name="name" value="override"/>
<!-- the age property value of 1 will be inherited from parent -->
</bean>
如上述代碼所示第练,子Bean如果沒有配置任何內(nèi)容阔馋,是直接使用父Bean的配置信息的,當(dāng)然了娇掏,如果配置了呕寝,將會覆蓋父Bean的配置。
子Bean會繼承父Bean的作用范圍婴梧,構(gòu)造參數(shù)值下梢,屬性值客蹋,和覆蓋父Bean的方法,可以增加新的值怔球。任何的作用域,初始化方法浮还,銷毀方法竟坛,或者靜態(tài)工廠方法配置,開發(fā)者都可以覆蓋父Bean的配置钧舌。
有一些配置都是從子Bean定義中讀取的:depends-on担汤,自動裝載模式,依賴檢查洼冻,單例崭歧,延遲初始化。
前面的例子通過使用abstract
標(biāo)簽來使父Bean抽象撞牢,如果父定義沒有指定類率碾,那么久需要使用屬性abstract
如下:
<bean id="inheritedTestBeanWithoutClass" abstract="true">
<property name="name" value="parent"/>
<property name="age" value="1"/>
</bean>
<bean id="inheritsWithClass" class="org.springframework.beans.DerivedTestBean"
parent="inheritedTestBeanWithoutClass" init-method="initialize">
<property name="name" value="override"/>
<!-- age will inherit the value of 1 from the parent bean definition-->
</bean>
父Bean是無法被實例化的,因為它是不完整的屋彪,會被標(biāo)志位abstract
所宰。當(dāng)使用abstract
的時候,其配置就作為純模板來使用了畜挥。如果嘗試在abctract
的父Bean中引用了其他的Bean或者調(diào)用getBean()
都會返回錯誤仔粥。容器內(nèi)部的preInstantiateSingletons()
方法會忽略掉那些定義為抽象的Bean。
ApplicationContext
會默認(rèn)預(yù)初始化所有的單例蟹但。因此躯泰,如果開發(fā)者配置了一個父Bean作為模板,而且其定義了指定的類华糖,那么開發(fā)者就必須配置抽象屬性為true
麦向,否則,應(yīng)用上下文會嘗試預(yù)初始化這個父Bean客叉。