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