Spring IoC容器之自定義bean的生命周期及定義繼承

一、自定義bean的生命周期

通過實(shí)現(xiàn)spring的InitializingBean和DisposableBean接口,可以讓容器來管理bean的生命周期。容器在調(diào)用afterPropertiesSet()方法后和調(diào)用destroy()方法前會允許bean在初始化和銷毀bean時執(zhí)行以下操作,但使用這些接口就與springAPI產(chǎn)生了耦合。

解耦合的處理方式有兩種:

\bullet 使用JSR-250的@PostConstruct和@PreDestroy注解是spring應(yīng)用生命周期回調(diào)的最佳實(shí)踐。

\bullet 使用init-method和destroy-method的定義來解耦spring接口。

spring框架使用BeanPostProcessor接口的實(shí)現(xiàn)來處理接口的回調(diào),BeanPostProcessor能找到并調(diào)用合適的方法。如果需要定制spring不直接提供的生命周期行為,可自行實(shí)現(xiàn)一個BeanPostProcessor。

除初始化回調(diào)和銷毀回調(diào)外,spring管理的對象也實(shí)現(xiàn)了Lifecycle接口,讓管理的對象在容器的生命周期內(nèi)啟動或關(guān)閉。

1、初始化回調(diào)

org.springframework.beans.factory.InitializingBean接口允許bean在所有必要依賴配置完成后執(zhí)行初始化bean。

接口定義

不建議使用InitializingBean接口,否則會將代碼耦合到spring特定接口上。使用@PostConstruct注解或指定一個POJO的實(shí)現(xiàn)方法的方式更好。在基于XML的配置元數(shù)據(jù)時可以使用init-method屬性來指定一個沒有參數(shù)的方法。使用Java配置時也可以使用@Bean中的init-method屬性初始化回調(diào)。示例如下:

<bean id="initBean" class="xx.InitBean" init-method="init" />

public class?InitBean {

? ? public void init(){...}

}

以下方式等效于以上方式,但會耦合到spring中:

<bean id="initBean" class="xx.InitBean" />

public class?InitBean implements InitializingBean?{

????public void afterPropertiesSet(){...}

}

2、銷毀回調(diào)

實(shí)現(xiàn)org.springframework.beans.factory.DisposableBean接口就可以讓容器通過回調(diào)來銷毀bean鎖引用的資源。

接口定義

與InitializingBean接口類似,同樣不推薦使用。使用@PreDestroy注解或指定一個bean支持的配置方法或在基于XML的配置元數(shù)據(jù)中,在bean標(biāo)簽上指定destroy-method屬性。在基于Java的配置中可以配置@Bean中的destroy-method屬性實(shí)現(xiàn)銷毀回調(diào)。示例如下:

<bean id="desBean" class="xx.DesBean" destroy-method="cleanup" />

public class?DesBean {

????public void cleanup(){...}

}

以下方式等效于以上方式,但會耦合到spring中:

<bean id="desBean" class="xx.DesBean" />

public class?DesBean?implements?DisposableBean?{

????public void?destroy(){...}

}

3、結(jié)合生命周期機(jī)制

spring2.5之后,有3種方式來控制bean的生命周期行為:

\bullet InitializingBean和DisposableBean回調(diào)接口

\bullet 自定義的init()和destroy()方法

\bullet 使用@PostConstruct和@PreDestroy注解

如果一個bean配置了多個生命周期機(jī)制,并且含有不同的方法名,執(zhí)行順序如下:

初始化時:

1)包含@PostConstruct注解的方法

2)在InitializingBean接口中的afterPropertiesSet()方法

3)自定義的init()方法

銷毀時:

1)包含@PreDestroy注解的方法

2)在DisposableBean接口中的destroy()方法

3)自定義的destroy()方法

4、啟動和關(guān)閉回調(diào)

Lifecycle接口中為任何有生命周期需求的對象定義了一些基本方法,spring管理的任何對象都可以實(shí)現(xiàn)其接口。當(dāng)ApplicationContext接收到了啟動或停止信號時,它會通知所有上下文包含的生命周期對象,通過LifecycleProcessor接口來串聯(lián)上下文中的Lifecycle來實(shí)現(xiàn)對象。

Lifecycle接口定義
LifecycleProcessor接口定義

LifecycleProcessor是對Lifecycle的擴(kuò)展,它增加了2個方法來對上下文的刷新和關(guān)閉作出反應(yīng)。

如果不同bean之間存在depends-on的關(guān)系,被依賴的一方需要更早的啟動或關(guān)閉,有時直接的依賴是未知的,開發(fā)者可能并不值得哪些類型需要更早的進(jìn)行初始化。SmartLifecycle接口定義了另外一種選項(xiàng),即其父接口Phased中的getPhase()方法。

SmartLifecycle接口定義??
Phased接口定義

當(dāng)啟動時,擁有最低phased的對象優(yōu)先啟動,當(dāng)關(guān)閉時,按相反的順序。如果一個對象實(shí)現(xiàn)了SmartLifecycle接口,其getPhase()返回了Integer.MIN_VALUE,就會讓該對象最早啟動,最晚銷毀。如果getPhase()返回了Integer.MAX_VALUE,就會讓該對象最晚啟動,最早銷毀。當(dāng)使用phased的值時,需要知道正常沒有實(shí)現(xiàn)SmartLifecycle的Lifecycle對象的默認(rèn)值,此值為0。任何負(fù)值都會將標(biāo)明對象在標(biāo)準(zhǔn)組件啟動前啟動,在標(biāo)準(zhǔn)組件銷毀后銷毀。

SmartLifecycle定義了一個stop()回調(diào)函數(shù),任何實(shí)現(xiàn)了其接口的方法都必須在關(guān)閉流程完成后調(diào)用回調(diào)中的run()方法,這樣可以使其異步關(guān)閉。LifecycleProcessor的默認(rèn)實(shí)現(xiàn)DefaultLifecycleProcessor會等到配置的時間超時后再調(diào)用回調(diào),每階段的超時默認(rèn)30S??梢酝ㄟ^定義一個名為lifecycleProcessor的bean來覆蓋默認(rèn)的生命周期處理器,若需要配置超時時間,可如下配置:

<bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor">

? ? <!--?超時時間,單位毫秒 -->

? ? <property name="timeoutPerShutdownPhase" value="10000" />

</bean>

SmartLifecycle接口定義回調(diào)方法來刷新和關(guān)閉上下文,如果關(guān)閉則說明stop()已被調(diào)用,就會驅(qū)動關(guān)閉流程;如果上下文正在關(guān)閉,就不會發(fā)生這種情況。刷新的回調(diào)會使用SmartLifecycle的另一特性:當(dāng)上下文刷新完畢就會調(diào)用回調(diào),默認(rèn)的生命周期處理器會檢查每一個SmartLifecycle對象的isAutoStartup()返回值。若為true,對象將會自動啟動而非等待明確的上下文調(diào)用,或調(diào)用自己的start()。phased的值及depends-on會決定對象啟動和銷毀的順序。

5、在非web應(yīng)用中關(guān)閉Spring IoC容器

在桌面客戶端等非web環(huán)境下使用Spring IoC容器,需要在JVM上注冊一個關(guān)閉的鉤子,確保在關(guān)閉IoC容器時能夠調(diào)用相關(guān)的銷毀方法來釋放引用的資源,同時也需要正確的配置和實(shí)現(xiàn)銷毀回調(diào)。

在ConfigurableApplicationContext接口調(diào)用registerShutdownHook()方法來注冊銷毀的鉤子。示例如下:

ConfigurableApplicationContext ctx = new?ClassPathXmlApplicationContext("spring.xml");

...

ctx.registerShutdownHook();

6、ApplicationContextAware和BeanNameAware

當(dāng)ApplicationContext在創(chuàng)建實(shí)現(xiàn)了org.springframework.context.ApplicationContextAware的對象時,該對象的實(shí)例會包含一個到ApplicationContext的引用。這樣bean可以通過編程的方式操作和創(chuàng)建ApplicationContext。

ApplicationContextAware接口定義

通過ApplicationContext接口或通過將引用轉(zhuǎn)換為已知接口的子類,如ConfigurableApplicationContext,其中一個用法是可以通過編程的方式來獲取其他bean。但不建議這樣做,這樣代碼會耦合到spring。ApplicationContext的其他方法可以提供資源訪問、發(fā)布應(yīng)用事件、進(jìn)入MessageSource等功能。

自動裝載也是獲得ApplicationContext的一種方式,構(gòu)造函數(shù)和通用類型的裝載方式,是指可以通過構(gòu)造函數(shù)或setter方法注入,也可以通過注解注入的方式。

當(dāng)ApplicationContext創(chuàng)建一個實(shí)現(xiàn)了org.springframework.beans.factory.BeanNameAware接口的類,這個類就可以針對其名稱進(jìn)行配置。這個回調(diào)的調(diào)用發(fā)生在屬性配置完成后,初始化回調(diào)之前,如InitializingBean.afterPropertiesSet()及自定義的初始化方法等。

BeanNameAware定義

7、其他Aware接口

spring還提供了其他Aware接口來讓bean通知容器,這些bean需要一些具體的注入的依賴信息,如下:

\bullet ApplicationContextAware:聲明的ApplicationContext

\bullet ApplicationEventPublisherAware:ApplicationContext中的事件發(fā)布器

\bullet BeanClassLoaderAware:加載bean使用的類加載器

\bullet BeanFactoryAware:聲明的BeanFactory

\bullet BeanNameAware:bean的名稱

\bullet LoadTimeWeaverAware:加載期間處理類定義的Weaver

\bullet MessageSourceAware:解析消息的配置策略

\bullet NotificationPublisherAware:spring JMX通知發(fā)布器

\bullet ResourceLoaderAware:配置的資源加載器

上面這些接口的不建議使用,因?yàn)槠鋾`反IOC原則。

二、定義繼承

bean的定義可以包含構(gòu)造方法參數(shù)、屬性值、容器特定的信息等。子bean定義可以從父bean定義的配置元數(shù)據(jù)來繼承,它可以覆蓋或添加一些所需的值。父子bean是一種典型的模板形式。

如果編程式的使用ApplicationContext接口,子bean的定義可以通過ChildBeanDefinition類來表示,但使用更多的是在類似于ClassPathXmlApplicationContext中聲明式的配置bean的定義。如果使用基于XML的配置時,可以在子bean中使用parent屬性,用于標(biāo)識父bean。

<bean id="testBean" abstract="true" class="org.springframework.beans.TestBean">

? <property name="name" value="parent"/>

? <property name="age" value="10"/>

</bean>

<bean id="testBean1" class="org.springframework.beans.TestBean1" parent="testBean" init-method="initialize">

? <property name="name" value="override"/>

</bean>

子bean如果沒有指定class,它將使用父bean定義的class或進(jìn)行重載。在重載情況下子bean必須與父bean兼容,即它必須接受父bean的屬性值。

子bean定義可以從父bean繼承作用域、構(gòu)造器參數(shù)、屬性值和可重寫的方法,還可以增加新值。開發(fā)者指定的任何作用域、初始化方法、銷毀方法、靜態(tài)工廠方法設(shè)置,都會覆蓋相應(yīng)的父bean設(shè)置。其余的設(shè)置總是取自子bean的定義,如depends-on、autowire mode、dependency check、singleton、scope和lazy init等。

上例中使用abstract屬性明確表明父bean是抽象的,如果父bean定義沒有明確指出所屬類,則需要標(biāo)記父bean定義為abstract。

<bean id="testBean" abstract="true">

? <property name="name" value="parent"/>

? <property name="age" value="10"/>

</bean>

<bean id="testBean1" class="org.springframework.beans.TestBean1" parent="testBean" init-method="initialize">

? <property name="name" value="override"/>

</bean>

上例中只有一個bean定義為abstract,它只能作為一個純粹的為子bean定義的bean模板。獨(dú)立使用這樣一個abstract的父bean,把它作為另一個bean的引用,或根據(jù)這個父bean的id顯式調(diào)用getBean(),都將返回錯誤。

ApplicationContext默認(rèn)會預(yù)實(shí)例化所有單例bean,如果把一個bean定義僅作為模板來用,同時給它指定了class屬性,就必須設(shè)置abstract=true,否則ApplicationContext就會預(yù)實(shí)例化這個abstract?bean。


--參考文獻(xiàn)《Srping5開發(fā)大全》

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容