?
This document uses PHP Chinese website manual Release
Spring提供了幾個標(biāo)志接口(marker interface),這些接口用來改變?nèi)萜髦衎ean的行為;它們包括InitializingBean
和DisposableBean
。實現(xiàn)這兩個接口的bean在初始化和析構(gòu)時容器會調(diào)用前者的afterPropertiesSet()
方法,以及后者的destroy()
方法。
Spring在內(nèi)部使用BeanPostProcessor
實現(xiàn)來處理它能找到的任何標(biāo)志接口并調(diào)用相應(yīng)的方法。如果你需要自定義特性或者生命周期行為,你可以實現(xiàn)自己的 BeanPostProcessor
。關(guān)于這方面更多的內(nèi)容可以看第?3.7?節(jié) “容器擴(kuò)展點”。
下面講述了幾個生命周期標(biāo)志接口。在附錄中會提供相關(guān)的示意圖來展示Spring如何管理bean,以及生命周期特性如何改變bean的內(nèi)在特性。
實現(xiàn)org.springframework.beans.factory.InitializingBean
接口允許容器在設(shè)置好bean的所有必要屬性后,執(zhí)行初始化事宜。InitializingBean
接口僅指定了一個方法:
void afterPropertiesSet() throws Exception;
通常,要避免使用InitializingBean
接口并且不鼓勵使用該接口,因為這樣會將代碼和Spring耦合起來,有一個可選的方案是,可以在Bean定義中指定一個普通的初始化方法,然后在XML配置文件中通過指定init-method
屬性來完成。如下面的定義所示:
<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耦合在一起。
實現(xiàn)org.springframework.beans.factory.DisposableBean
接口的bean允許在容器銷毀該bean的時候獲得一次回調(diào)。DisposableBean
接口也只規(guī)定了一個方法:
void destroy() throws Exception;
通常,要避免使用DisposableBean
標(biāo)志接口而且不鼓勵使用該接口,因為這樣會將代碼與Spring耦合在一起,有一個可選的方案是,在bean定義中指定一個普通的析構(gòu)方法,然后在XML配置文件中通過指定destroy-method
屬性來完成。如下面的定義所示:
<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
回調(diào)接口來編寫初始化和析構(gòu)方法回調(diào),會發(fā)現(xiàn)自己正在編寫的方法,其名稱莫過于init()
, initialize()
,dispose()
等等。這種生命周期回調(diào)方法的名稱最好在一個項目范圍內(nèi)標(biāo)準(zhǔn)化,這樣團(tuán)隊中的開發(fā)人員就可以使用同樣的方法名稱,并且確保了某種程度的一致性。
Spring容器通過配置可以實現(xiàn)對每個 bean初始化時的查找和銷毀時的回調(diào)調(diào)用。這也就是說,一個應(yīng)用的開發(fā)者可以借助于初始化的回調(diào)方法init()
輕松的寫一個類(不必想XML配置文件那樣為每個bean都配置一個'init-method="init"'
屬性)。Spring IoC容器在創(chuàng)建bean的時候將調(diào)用這個方法 (這和之前描述的標(biāo)準(zhǔn)生命周期回調(diào)一致)。
為了完全弄清如何使用該特性,讓我們看一個例子。出于示范的目的,假設(shè)一個項目的編碼規(guī)范中約定所有的初始化回調(diào)方法都被命名為init()
而析構(gòu)回調(diào)方法被命名為destroy()
。遵循此規(guī)則寫成的類如下所示:
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容器在bean創(chuàng)建和裝配的時候會將'init'
方法作為實例化回調(diào)方法。如果類有這個方法,則會在適當(dāng)?shù)臅r候執(zhí)行。
銷毀回調(diào)方法配置是相同的 (XML配置),在頂級的<beans/>
元素中使用 'default-destroy-method'
屬性。
使用這個功能可以把你從位每個bean指定初始化和銷毀回調(diào)的繁雜工作中解救出來。為了一致性,應(yīng)該強(qiáng)制性的為初始化和銷毀回調(diào)方法采用一致的命名規(guī)則。
當(dāng)已經(jīng)存在的類的初始化方法的命名規(guī)則與慣例有差異的時候,你應(yīng)該始終使用<bean/>
元素中的'init-method'
和'destroy-method'
屬性(在XML配置中)來覆蓋默認(rèn)的方式。
最后,請注意Spring容器保證在bean的所有依賴都滿足后立即執(zhí)行配置的初始化回調(diào)。這意味著初始化回調(diào)在原生bean上調(diào)用,這也意味著這個時候任何諸如AOP攔截器之類的將不能被應(yīng)用。一個目標(biāo)bean是首先完全創(chuàng)建,然后才應(yīng)用諸如AOP代理等攔截器鏈。注意,如果目標(biāo)bean和代理是分開定義了,你的代碼甚至可以繞開代理直接和原生bean通信。因此,在初始化方法上使用攔截器將產(chǎn)生未知的結(jié)果,因為這將目標(biāo)bean和它的代理/攔截器的生命周期綁定并且留下了和初始bean直接通信這樣奇怪的方式。
As of Spring 2.5, there are three options for controlling bean
lifecycle behavior: the
InitializingBean
and
DisposableBean
callback interfaces; custom init()
and
destroy()
methods; and the
@PostConstruct
and @PreDestroy
annotations.
在Spring2.5中有三種方式可以控制bean的生命周期行為:
InitializingBean
和
DisposableBean
回調(diào)接口;自定義init()
和
destroy()
方法;
@PostConstruct
和@PreDestroy
annotations.
當(dāng)組合不同的生命周期機(jī)制時 - 例如,類層次中使用了不同的生命周期機(jī)制 - 開發(fā)者必須注意這些機(jī)制的應(yīng)用順序,下面是初始化方法中的順序:
@PostConstruct
元注釋
InitializingBean
的afterPropertiesSet()
定義
自定義init()
方法配置
析構(gòu)方法調(diào)用順序是相同的:
@PreDestroy
元注釋
DisposableBean
的destroy()
定義
自定義destroy()
方法
如果bean存在多種的生命周期機(jī)制配置并且每種機(jī)制都配置為不同的方法名,
那所有配置的方法將會按照上面的順利執(zhí)行。然而如果配置了相同的方法名 - 例如,
init()
初始化方法 - 采用多種機(jī)制配置后,只會執(zhí)行一次。
在基于web的ApplicationContext
實現(xiàn)中已有相應(yīng)的代碼來處理關(guān)閉web應(yīng)用時如何恰當(dāng)?shù)仃P(guān)閉Spring IoC容器。
如果你正在一個非web應(yīng)用的環(huán)境下使用Spring的IoC容器,例如在桌面富客戶端環(huán)境下,你想讓容器優(yōu)雅的關(guān)閉,并調(diào)用singleton bean上的相應(yīng)析構(gòu)回調(diào)方法,你需要在JVM里注冊一個“關(guān)閉鉤子”(shutdown hook)。這一點非常容易做到,并且將會確保你的Spring IoC容器被恰當(dāng)關(guān)閉,以及所有由單例持有的資源都會被釋放(當(dāng)然,為你的單例配置銷毀回調(diào),并正確實現(xiàn)銷毀回調(diào)方法,依然是你的工作)。
為了注冊“關(guān)閉鉤子”,你只需要簡單地調(diào)用在AbstractApplicationContext
實現(xiàn)中的registerShutdownHook()
方法即可。也就是:
import org.springframework.context.support.AbstractApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public final class Boot { public static void main(final String[] args) throws Exception { AbstractApplicationContext 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... } }
對于實現(xiàn)了org.springframework.beans.factory.BeanFactoryAware
接口的類,當(dāng)它被BeanFactory創(chuàng)建后,它會擁有一個指向創(chuàng)建它的BeanFactory的引用。
public interface BeanFactoryAware { void setBeanFactory(BeanFactory beanFactory) throws BeansException; }
這樣bean可以以編程的方式操控創(chuàng)建它們的BeanFactory
,當(dāng)然我們可以將引用的BeanFactory造型(cast)為已知的子類型來獲得更多的功能。它主要用于通過編程來取得BeanFactory所管理的其他bean。雖然在有些場景下這個功能很有用,但是一般來說應(yīng)該盡量避免使用,因為這樣將使代碼與Spring耦合在一起,而且也有違反轉(zhuǎn)控制的原則(協(xié)作者應(yīng)該作為屬性提供給bean)。
與BeanFactoryAware
等效的另一種選擇是使用org.springframework.beans.factory.config.ObjectFactoryCreatingFactoryBean
。不過該方法依然沒有降低與Spring的耦合,但是它并沒有像BeanFactoryAware
那樣,違反IoC原則。)
ObjectFactoryCreatingFactoryBean
是
FactoryBean
的一個實現(xiàn),它返回一個指向工廠對象的引用,該對象將執(zhí)行bean的查找。ObjectFactoryCreatingFactoryBean
類實現(xiàn)了BeanFactoryAware
接口;被實際注入到客戶端bean的是ObjectFactory
接口的一個實例。這是Spring提供的一個接口(因而依舊沒有完全與Spring解耦),客戶端可以使用ObjectFactory
的getObject()
方法來查找bean(在其背后,ObjectFactory
實例只是簡單的將調(diào)用委派給BeanFactory
,讓其根據(jù)bean的名稱執(zhí)行實際的查找)。你要做的全部事情就是給ObjectFactoryCreatingFactoryBean
提供待查找bean的名字。讓我們看一個例子:
package x.y; public class NewsFeed { private String news; public void setNews(String news) { this.news = news; } public String getNews() { return this.toString() + ": '" + news + "'"; } }
package x.y; import org.springframework.beans.factory.ObjectFactory; public class NewsFeedManager { private ObjectFactory factory; public void setFactory(ObjectFactory factory) { this.factory = factory; } public void printNews() { // here is where the lookup is performed; note that there is no // need to hard code the name of the bean that is being looked up... NewsFeed news = (NewsFeed) factory.getObject(); System.out.println(news.getNews()); } }
下述是XML配置:
<beans> <bean id="newsFeedManager" class="x.y.NewsFeedManager"> <property name="factory"> <bean class="org.springframework.beans.factory.config.ObjectFactoryCreatingFactoryBean"> <property name="targetBeanName"> <idref local="newsFeed" /> </property> </bean> </property> </bean> <bean id="newsFeed" class="x.y.NewsFeed" scope="prototype"> <property name="news" value="... that's fit to print!" /> </bean> </beans>
這里有一個測試用的小程序:在NewsFeedManager
的printNews()
方法里,每次針對被注入的ObjectFactory
的調(diào)用,實際上返回的是一個新的(prototype)newsFeed
bean實例。
import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import x.y.NewsFeedManager; public class Main { public static void main(String[] args) throws Exception { ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml"); NewsFeedManager manager = (NewsFeedManager) ctx.getBean("newsFeedManager"); manager.printNews(); manager.printNews(); } }
上述程序的執(zhí)行輸出如下所示(當(dāng)然,返回結(jié)果會根據(jù)你機(jī)器的不同而不同)
x.y.NewsFeed@1292d26: '... that's fit to print!' x.y.NewsFeed@5329c5: '... that's fit to print!'
在Spring2.5中,可以利用BeanFactory
的自動裝配作為實現(xiàn) BeanFactoryAware
接口的可選方式。
"傳統(tǒng)"的constructor
和byType
自動裝配模式(在第?3.3.5?節(jié) “自動裝配(autowire)協(xié)作者”中有描述)對無論是構(gòu)造器參數(shù)或setter方法都能提供 BeanFactory
類型的 依賴。這有更多的靈活性(包括自動裝配屬性和多參數(shù)方法)。如果使用新的基于元注釋的自動裝配特性,只要屬性、
構(gòu)造器、方法包含有@Autowired
元注釋時,BeanFactory
將會自動裝配到對應(yīng)的屬性、構(gòu)造器、方法中。請參閱第?3.11.1?節(jié) “@Autowired
”。