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