前文已描寫了Bean的作用域,本文將描寫B(tài)ean的1些生命周期作用,配置還有Bean的繼承。
開發(fā)者通過(guò)實(shí)現(xiàn)Spring的InitializeingBean
和DisposableBean
接口,就能夠讓容器來(lái)管理Bean的生命周期。容器會(huì)調(diào)用afterPropertiesSet()
前和destroy()
后才會(huì)允許Bean在初始化和燒毀Bean的時(shí)候履行1些操作。
JSR⑵50的
@PostConstruct
和@PreDestroy
注解就是現(xiàn)代Spring利用生命周期回調(diào)的最好實(shí)踐。使用這些注解意味著Bean不在耦合在Spring特定的接口上。詳細(xì)內(nèi)容,后續(xù)將會(huì)介紹。
如果開發(fā)者不想使用JSR⑵50的注解,依然可以斟酌使用init-method
和destroy-method
定義來(lái)解耦。
內(nèi)部來(lái)講,Spring框架使用BeanPostProcessor
的實(shí)現(xiàn)來(lái)處理任何接口的回調(diào),BeanPostProcessor
能夠找到并調(diào)用適合的方法。如果開發(fā)者需要1些Spring其實(shí)不直接提供的生命周期行動(dòng),開發(fā)者可以自行實(shí)現(xiàn)1個(gè)BeanPostProcessor
。更多的信息可以參考后面的容器擴(kuò)大點(diǎn)。
除初始化和燒毀回調(diào),Spring管理的對(duì)象也實(shí)現(xiàn)了Lifecycle
接口來(lái)讓管理的對(duì)象在容器的生命周期內(nèi)啟動(dòng)和關(guān)閉。
生命周期回調(diào)在本節(jié)會(huì)進(jìn)行詳細(xì)描寫。
org.springframework.beans.factory.InitializingBean
接口允許Bean在所有的必要的依賴配置配置完成后來(lái)履行初始化Bean的操作。InitializingBean
接口中特指了1個(gè)方法:
void afterPropertiesSet() throws Exception;
Spring團(tuán)隊(duì)建議開發(fā)者不要使用InitializingBean
接口,由于這樣會(huì)沒(méi)必要要的將代碼耦合到Spring之上。而通過(guò)使用@PostConstruct
注解或指定1個(gè)POJO的實(shí)現(xiàn)方法,比實(shí)現(xiàn)接口要更好。在基于XML的配置元數(shù)據(jù)上,開發(fā)者可使用init-method
屬性來(lái)指定1個(gè)沒(méi)有參數(shù)的方法。使用Java配置的開發(fā)者可使用@Bean
當(dāng)中的initMethod
屬性,比如以下:
<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
public class ExampleBean {
public void init() {
// do some initialization work
}
}
與以下代碼1樣效果:
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements InitializingBean {
public void afterPropertiesSet() {
// do some initialization work
}
}
但是前1個(gè)版本的代碼是沒(méi)有耦合到Spring的。
實(shí)現(xiàn)了org.springframework.beans.factory.DisposableBean
接口的Bean就可以通讓容器通過(guò)回調(diào)來(lái)燒毀Bean所用的資源。DisposableBean
接口包括了1個(gè)方法:
void destroy() throws Exception;
同InitializingBean一樣,Spring團(tuán)隊(duì)依然不建議開發(fā)者來(lái)使用DisposableBean
回調(diào)接口,由于這樣會(huì)將開發(fā)者的代碼耦合到Spring代碼上。換種方式,比如使用@PreDestroy
注解或指定1個(gè)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)
}
}
但是第1段代碼是沒(méi)有耦合到Spring的。
<bean/>
標(biāo)簽的destroy-method
可以被配置為特殊指定的值,來(lái)方便讓Spring能夠自動(dòng)的檢查到close
或shutdown
方法(可以實(shí)現(xiàn)java.lang.AutoCloseable
或java.io.Closeable
都會(huì)匹配。)這個(gè)特殊指定的值可以配置到<beans/>
的default-destroy-method
來(lái)讓所有的Bean實(shí)現(xiàn)這個(gè)行動(dòng)。
當(dāng)開發(fā)者不使用Spring獨(dú)有的InitializingBean
和DisposableBean
回調(diào)接口來(lái)實(shí)現(xiàn)初始化和燒毀方法的時(shí)候,開發(fā)者通常定義的方法名字都是好似init()
,initialize()
或是dispose()
等等。那末,想這類的方法就能夠標(biāo)準(zhǔn)化,來(lái)讓所有的開發(fā)者都使用1樣的名字來(lái)確保1致性。
開發(fā)者可以配置Spring容器來(lái)針對(duì)每個(gè)Bean都查找這類名字的初始化和燒毀回調(diào)函數(shù)。也就是說(shuō),任何的1個(gè)利用開發(fā)者,都會(huì)在利用的類中使用1個(gè)叫init()
的初始化回調(diào),而不需要在每一個(gè)Bean中定義init-method="init"
這中屬性。Spring IoC容器會(huì)在Bean創(chuàng)建的時(shí)候調(diào)用那個(gè)方法(就如前面描寫的標(biāo)準(zhǔn)生命周期1樣。)這個(gè)特性也強(qiáng)迫開發(fā)者為其他的初始化和燒毀回調(diào)函數(shù)使用一樣的名字。
假定開發(fā)者的初始化回調(diào)方法名字為init()
而燒毀的回調(diào)方法為destroy()
。那末開發(fā)者的類就會(huì)好似以下的代碼:
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
屬性會(huì)讓Spring IoC容器辨認(rèn)叫做init
的方法來(lái)作為Bean的初始化回調(diào)方法。當(dāng)Bean創(chuàng)建和裝載以后,如果Bean有這么1個(gè)方法的話,Spring容器就會(huì)在適合的時(shí)候調(diào)用。
類似的,開發(fā)者也能夠配置默許燒毀回調(diào)函數(shù),基于XML的配置就在<beans/>
標(biāo)簽上面使用default-destroy-method
屬性。
當(dāng)存在1些Bean的類有了1些回調(diào)函數(shù),而和配置的默許回調(diào)函數(shù)不同的時(shí)候,開發(fā)者可以通過(guò)特指的方式來(lái)覆蓋掉默許的回調(diào)函數(shù)。以XML為例,就是通過(guò)使用<bean>
標(biāo)簽的init-method
和destroy-method
來(lái)覆蓋掉<beans/>
中的配置。
Spring容器會(huì)做出以下保證,Bean會(huì)在裝載了所有的依賴以后,立刻就開始履行初始化回調(diào)。這樣的話,初始化回調(diào)只會(huì)在直接的Bean援用裝載好后調(diào)用,而AOP攔截器還沒(méi)有利用到Bean上。首先目標(biāo)Bean會(huì)完全初始化好,然后,AOP代理和其攔截鏈才能利用。如果目標(biāo)Bean和代理是分開定義的,那末開發(fā)者的代碼乃至可以跳過(guò)AOP而直接和援用的Bean交互。因此,在初始化方法中利用攔截器會(huì)前后矛盾,由于這樣做耦合了目標(biāo)Bean的生命周期和代理/攔截器,還會(huì)由于同Bean直接交互而產(chǎn)生奇怪的現(xiàn)象。
在Spring 2.5以后,開發(fā)者有3種選擇來(lái)控制Bean的生命周期行動(dòng):
InitializingBean
和DisposableBean
回調(diào)接口init()
和destroy
方法@PostConstruct
和@PreDestroy
注解開發(fā)者也能夠在Bean上聯(lián)合這些機(jī)制1起使用
如果Bean配置了多個(gè)生命周期機(jī)制,而且每一個(gè)機(jī)制配置了不同的方法名字,那末每一個(gè)配置的方法會(huì)依照后面描寫的順序來(lái)履行。但是,如果配置了相同的名字,比如說(shuō)初始化回調(diào)為
init()
,在不止1個(gè)生命周期機(jī)制配置為這個(gè)方法的情況下,這個(gè)方法只會(huì)履行1次。
如果1個(gè)Bean配置了多個(gè)生命周期機(jī)制,并且含有不同的方法名,履行的順序以下:
@PostConstruct
注解的方法InitializingBean
接口中的afterPropertiesSet()
方法init()
方法燒毀方法的履行順序和初始化的履行順序相同:
@PreDestroy
注解的方法DisposableBean
接口中的destroy()
方法destroy()
方法Lifecycle
接口中為任何有自己生命周期需求的對(duì)象定義了基本的方法(比如啟動(dòng)和停止1些后臺(tái)進(jìn)程):
public interface Lifecycle {
void start();
void stop();
boolean isRunning();
}
任何Spring管理的對(duì)象都可實(shí)現(xiàn)上面的接口。那末當(dāng)ApplicationContext
本身遭到了啟動(dòng)或停止的信號(hào)時(shí),ApplicationContext
會(huì)通過(guò)拜托LifecycleProcessor
來(lái)串連上下文中的Lifecycle
的實(shí)現(xiàn)。
public interface LifecycleProcessor extends Lifecycle {
void onRefresh();
void onClose();
}
從上面代碼我們可以發(fā)現(xiàn)LifecycleProcessor
是Lifecycle
接口的擴(kuò)大。LifecycleProcessor
增加了另外的兩個(gè)方法來(lái)針對(duì)上下文的刷新和關(guān)閉做出反應(yīng)。
常規(guī)的
org.springframework.context.Lifecycle
接口只是為明確的開始/停止通知提供1個(gè)契約,而其實(shí)不表示在上下文刷新時(shí)自動(dòng)開始。斟酌實(shí)現(xiàn)org.springframework.context.SmartLifecycle
接口可以取代在某個(gè)Bean的自動(dòng)啟動(dòng)進(jìn)程(包括啟動(dòng)階段)中的細(xì)粒度控制。同時(shí),停止通知其實(shí)不能保證在燒毀之前出現(xiàn):在正常的關(guān)閉情況下,所有的Lifecycle
Bean都會(huì)在燒毀回調(diào)準(zhǔn)備好之前收到停止停止,但是,在上下文存活期的熱刷新或停止刷新嘗試的時(shí)候,只會(huì)調(diào)用燒毀方法。
啟動(dòng)和關(guān)閉調(diào)用是很重要的。如果不同的Bean之間存在depends-on
的關(guān)系的話,被依賴的1方需要更早的啟動(dòng),而且關(guān)閉的更早。但是,有的時(shí)候直接的依賴是未知的,而開發(fā)者僅僅知道哪種類型需要更早進(jìn)行初始化。在這類情況下,SmartLifecycle
接口定義了另外一種選項(xiàng),就是其父接口Phased
中的getPhase()
方法。
public interface Phased {
int getPhase();
}
public interface SmartLifecycle extends Lifecycle, Phased {
boolean isAutoStartup();
void stop(Runnable callback);
}
當(dāng)啟動(dòng)時(shí),具有最低的phased
的對(duì)象優(yōu)先啟動(dòng),而當(dāng)關(guān)閉時(shí),是相反的順序。因此,如果1個(gè)對(duì)象實(shí)現(xiàn)了SmartLifecycle
然后令其getPhase()
方法返回了Integer.MIN_VALUE
的話,就會(huì)讓該對(duì)象最早啟動(dòng),而最晚燒毀。明顯,如果getPhase()
方法返回了Integer.MAX_VALUE
就說(shuō)明了該對(duì)象會(huì)最晚啟動(dòng),而最早燒毀。當(dāng)斟酌到使用phased
的值得時(shí)候,也同時(shí)需要了解正常沒(méi)有實(shí)現(xiàn)SmartLifecycle
的Lifecycle
對(duì)象的默許值,這個(gè)值為0。因此,任何負(fù)值將標(biāo)兵對(duì)象會(huì)在標(biāo)準(zhǔn)組件啟動(dòng)之前啟動(dòng),在標(biāo)準(zhǔn)組件燒毀以后再進(jìn)行燒毀。
SmartLifecycle
接口也定義了1個(gè)stop
的回調(diào)函數(shù)。任何實(shí)現(xiàn)了SmartLifecycle
接口的函數(shù)都必須在關(guān)閉流程完成以后調(diào)用回調(diào)中的run()
方法。這樣做可以是能異步關(guān)閉。而LifecycleProcessor
的默許實(shí)現(xiàn)DefaultLifecycleProcessor
會(huì)等到配置的超時(shí)時(shí)間以后再調(diào)用回調(diào)。默許的每階段的超時(shí)時(shí)間為30秒。開發(fā)者可以通過(guò)定義1個(gè)叫做lifecycleProcessor
的Bean來(lái)覆蓋默許的生命周期處理器。如果開發(fā)者需要配置超時(shí)時(shí)間,可以通過(guò)以下代碼:
<bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor">
<!-- timeout value in milliseconds -->
<property name="timeoutPerShutdownPhase" value="10000"/>
</bean>
和前文提到的,LifecycleProcessor
接口定義了回調(diào)方法來(lái)刷新和關(guān)閉山下文。關(guān)閉的話,如果stop()
方法已明確調(diào)用了,那末就會(huì)驅(qū)動(dòng)關(guān)閉的流程,但是如果是上下文關(guān)閉就不會(huì)產(chǎn)生這類情況。而刷新的回調(diào)會(huì)使能SmartLifecycle
的另外一個(gè)特性。當(dāng)上下文刷新終了(所有的對(duì)象已實(shí)例化并初始化),那末就會(huì)調(diào)用回調(diào),默許的生命周期處理器會(huì)檢查每個(gè)SmartLifecycle
對(duì)象的isAutoStartup()
返回的Bool值。如果為真,對(duì)象將會(huì)自動(dòng)啟動(dòng)而不是等待明確的上下文調(diào)用,或調(diào)用自己的start()
方法(不同于上下文刷新,標(biāo)準(zhǔn)的上下文實(shí)現(xiàn)是不會(huì)自動(dòng)啟動(dòng)的)。phased
的值和depends-on
關(guān)系會(huì)決定對(duì)象啟動(dòng)和燒毀的順序。
這1部份只是針對(duì)非Web的利用。Spring的基于web的
ApplicationContext
實(shí)現(xiàn)已有代碼在web利用關(guān)閉的時(shí)候能夠優(yōu)雅的關(guān)閉Spring IoC容器。
如果開發(fā)者在非web利用環(huán)境使用Spring IoC容器的話, 比如,在桌面客戶真?zhèn)€環(huán)境下,開發(fā)者需要在JVM上注冊(cè)1個(gè)關(guān)閉的鉤子。來(lái)確保在關(guān)閉Spring IoC容器的時(shí)候能夠調(diào)用相干的燒毀方法來(lái)釋放掉對(duì)應(yīng)的資源。固然,開發(fā)者也必須要正確的配置和實(shí)現(xiàn)那些燒毀回調(diào)。
開發(fā)者可以在ConfigurableApplicationContext
接口調(diào)用registerShutdownHook()
來(lái)注冊(cè)燒毀的鉤子:
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)建實(shí)現(xiàn)了org.springframework.context.ApplicationContextAware
接口的對(duì)象時(shí),該對(duì)象的實(shí)例會(huì)包括1個(gè)到ApplicationContext
的援用。
public interface ApplicationContextAware {
void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}
這樣Bean就可以夠通過(guò)編程的方式操作和創(chuàng)建ApplicationContext
了。通過(guò)ApplicationContext
接口,或通過(guò)將援用轉(zhuǎn)換成已知的接口的子類,比如ConfigurableApplicationContext
就可以夠顯式1些額外的功能。其中的1個(gè)用法就是可以通過(guò)編程的方式來(lái)獲得其他的Bean。有的時(shí)候這個(gè)能力很有用,但是,Spring團(tuán)隊(duì)推薦最好避免這樣做,由于這樣會(huì)耦合代碼到Spring上,同時(shí)也沒(méi)有遵守IoC的風(fēng)格。其他的ApplicationContext
的方法可以提供1些到資源的訪問(wèn),發(fā)布利用事件,或進(jìn)入MessageSource
。這些信息在后續(xù)的針對(duì)ApplicationContext
的描寫中會(huì)講到。
在Spring 2.5版本,自動(dòng)裝載也是取得ApplicationContext
的1種方式。傳統(tǒng)的構(gòu)造函數(shù)和通過(guò)類型的裝載方式(前文Spring核心技術(shù)IoC容器(4)有相干描寫)可以通過(guò)構(gòu)造函數(shù)或是setter方法的方式注入。開發(fā)者也能夠通過(guò)注解注入的方式使用更多的特性。
當(dāng)ApplicationContext
創(chuàng)建了1個(gè)實(shí)現(xiàn)了org.springframework.beans.factory.BeanNameAware
接口的類,那末這個(gè)類就能夠針對(duì)其名字進(jìn)行配置。
public interface BeanNameAware {
void setBeanName(string name) throws BeansException;
}
這個(gè)回調(diào)的調(diào)用途于屬性配置完以后,但是初始化回調(diào)之前,比如InitializingBean
的afterPropertiesSet()
方法和自定義的初始化方法等。
Aware
接口除上面描寫的兩種Aware接口,Spring還提供了1系列的Aware
接口來(lái)讓Bean告知容器,這些Bean需要1些具體的基礎(chǔ)設(shè)施信息。最重要的1些Aware
接口都在下面表中進(jìn)行了描寫:
名字 | 注入的依賴 |
---|---|
ApplicationContextAware |
聲明的ApplicationContext |
ApplicationEventPlulisherAware |
ApplicationContext 中的事件發(fā)布器 |
BeanClassLoaderAware |
加載Bean使用的類加載器 |
BeanFactoryAware |
聲明的BeanFactory |
BeanNameAware |
Bean的名字 |
BootstrapContextAware |
容器運(yùn)行的資源適配器BootstrapContext ,通常僅在JCA環(huán)境下有效 |
LoadTimeWeaverAware |
加載期間處理類定義的weaver |
MessageSourceAware |
解析消息的配置策略 |
NotificationPublisherAware |
Spring JMX通知發(fā)布器 |
PortletConfigAware |
容器當(dāng)前運(yùn)行的PortletConfig ,僅在web下的Spring ApplicationContext 中可見 |
PortletContextAware |
容器當(dāng)前運(yùn)行的PortletContext ,僅在web下的Spring ApplicationContext 中可見 |
ResourceLoaderAware |
配置的資源加載器 |
ServletConfigAware |
容器當(dāng)前運(yùn)行的ServletConfig ,僅在web下的Spring ApplicationContext 中可見 |
ServletContextAware |
容器當(dāng)前運(yùn)行的ServletContext ,僅在web下的Spring ApplicationContext 中可見 |
再次的聲明,上面這些接口的使用時(shí)違背IoC原則的,除非必要,最好不要使用。
Bean的定義可以包括很多的配置信息,包括構(gòu)造參數(shù),屬性等等,也能夠包括1些容器指定的信息,比如初始化方法,工廠方法等等。子Bean會(huì)繼承父Bean的配置信息。子Bean也能夠覆蓋父Bean的1些值,或增加1些值。通過(guò)定義父Bean和子Bean可以減少配置內(nèi)容,是1種高效的模板性能。
如果開發(fā)者通過(guò)編程的方式跟ApplicationContext
交換,就會(huì)知道子Bean是通過(guò)ChildBeanDefinition
類表示的。大多數(shù)的開發(fā)者不需要再這個(gè)級(jí)別上來(lái)跟子Bean定義交互,只需要在ClassPathXmlApplicationContext
中顯式的配置Bean就能夠了。當(dāng)使用基于XML的元數(shù)據(jù)配置的時(shí)候,開發(fā)者通過(guò)使用parent
屬性來(lái)定義子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如果沒(méi)有配置任何內(nèi)容,是直接使用父Bean的配置信息的,固然了,如果配置了,將會(huì)覆蓋父Bean的配置。
子Bean會(huì)繼承父Bean的作用范圍,構(gòu)造參數(shù)值,屬性值,和覆蓋父Bean的方法,可以增加新的值。任何的作用域,初始化方法,燒毀方法,或靜態(tài)工廠方法配置,開發(fā)者都可以覆蓋父Bean的配置。
有1些配置都是從子Bean定義中讀取的:depends-on,自動(dòng)裝載模式,依賴檢查,單例,延遲初始化。
前面的例子通過(guò)使用abstract
標(biāo)簽來(lái)使父Bean抽象,如果父定義沒(méi)有指定類,那末久需要使用屬性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是沒(méi)法被實(shí)例化的,由于它是不完全的,會(huì)被標(biāo)志位abstract
。當(dāng)使用abstract
的時(shí)候,其配置就作為純模板來(lái)使用了。如果嘗試在abctract
的父Bean中援用了其他的Bean或調(diào)用getBean()
都會(huì)返回毛病。容器內(nèi)部的preInstantiateSingletons()
方法會(huì)疏忽掉那些定義為抽象的Bean。
ApplicationContext
會(huì)默許預(yù)初始化所有的單例。因此,如果開發(fā)者配置了1個(gè)父Bean作為模板,而且其定義了指定的類,那末開發(fā)者就必須配置抽象屬性為true
,否則,利用上下文會(huì)嘗試預(yù)初始化這個(gè)父Bean。
上一篇 MySQL查詢高速緩沖詳解
下一篇 如何解決atom無(wú)法安裝package的問(wèn)題(gyp WARN install got an error, rolling back install)