Spring 5.0.0框架介紹_中文版_3.6

weixin_34208283發表於2016-10-17

文章作者:Tyan
部落格:noahsnail.com | CSDN | 簡書

3.6 定製bean特性

3.6.1 生命週期回撥

為了與容器中bean生命週期的管理進行互動,你可以實現Spring的InitializingBeanDisposableBean介面。當初始化beans時容器會呼叫InitializingBean中的afterPropertiesSet()方法,當銷燬beans時容器會呼叫DisposableBean中的destroy()方法,在這兩個方法中bean可以執行特定的行為。

在現代Spring應用中,通常認為JSR-250的@PostConstruct@PreDestroy註解是最佳實踐接收生命週期回撥函式的方法。使用這些註解意味著你的bean沒有耦合Spring特定的介面。更多細節請看3.9.8小節,"@PostConstruct和@PreDestroy"。

如果你不想使用JSR-250註解,但你仍要注意解耦,可以考慮使用物件定義後設資料中的初始化方法和銷燬方法。

在Spring內部,Spring框架使用BeanPostProcessor實現來處理任何它能發現的回撥介面並呼叫合適的方法。如果你需要定製Spring不能提供的開箱即用的功能或其它生命週期行為,你可以自己實現BeanPostProcessor。更多資訊請看3.8小節,"容器擴充套件點"。

除了初始化回撥函式和銷燬回撥函式之外,Spring管理的物件也可以實現Lifecycle介面,這些物件可以參與容器自身生命週期驅動的啟動和關閉過程。

本節描述了生命週期回撥介面。

初始化回撥函式

org.springframework.beans.factory.InitializingBean介面在容器設定了bean所有的必須屬性之後,允許bean執行初始化工作。InitializingBean介面指定了一個方法:

void afterPropertiesSet() throws Exception;

建議你不使用InitializingBean介面,因為它對程式碼與Spring進行了不必要的耦合。作為一種替代方法,你可以使用@PostConstruct註解或指定一個POPJO的初始化方法。在基於XML配置後設資料的情況下,你可以使用init-method特性來指定方法的名稱,方法是沒有返回值和引數的。如果使用Java配置,你可以使用@BeaninitMethod特性,請看"接收生命週期回撥函式"小節。例如,下面的程式碼:

<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
public class ExampleBean {

    public void init() {
        // do some initialization work
    }

}

等價於:

public class AnotherExampleBean implements InitializingBean {

    public void afterPropertiesSet() {
        // do some initialization work
    }

}

但沒有與Spring程式碼耦合。

銷燬回撥函式

實現org.springframework.beans.factory.DisposableBean介面允許容器包含的bean銷燬時呼叫回撥函式。DisposableBean介面指定了一個方法:

void destroy() throws Exception;

建議你不使用DisposableBean回撥介面,因為它對程式碼與Spring進行了不必要的耦合。作為一種替代方法,你可以使用@PreDestroy註解或指定一個bean定義支援的通用方法。在基於XML配置後設資料的情況下,你可以使用<bean/>destroy-method特性。如果使用Java配置,你可以使用@BeandestroyMethod特性,請看"接收生命週期回撥"小節。例如,下面的定義:

<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特定的InitializingBeanDisposableBean回撥介面,自己編寫方法,方法名通常為init()initialize()dispose()等等。理想情況下,這種生命週期回撥方法的名稱在整個工程中是標準化的,以便所有開發人員使用同樣的方法名稱,保證一致性。

你可以配置Spring容器查詢每個bean的初始化方法和析構方法時的名字。這意味著,作為一個應用開發者,你可以編寫應用程式類並使用名為init()的初始化回撥方法,而不必在每個bean定義中配置init-method="init"特性。當bean建立時,Spring Ioc容器呼叫這個方法(按照前面描述的標準生命週期回撥約定)。這個功能也強制了初始化方法和析構方法命名規範的一致性。

假設你的初始化回撥方法名為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容器將beans中的名為init的方法識別為初始化回撥方法。當一個bean建立和組裝時,如果bean類有這樣一個方法,它會在恰當的時間被呼叫。

在bean被提供了所有依賴之後,Spring容器確保會立刻呼叫配置的初始化回撥方法。因此初始化回撥會在原生bean引用上呼叫,這意味著AOP攔截器等仍不能應用到bean中。首先要完整的建立目標bean,然後才會應用AOP代理(例如)等攔截器鏈。如果分別定義了目標bean和代理,你的程式碼甚至能繞過代理直接與原生的目標bean進行互動。將攔截器應用到初始化方法上可能會產生不一致性,因為這樣做會使目標bean的生命週期與它的代理/攔截器相耦合,當你的程式碼與原生目標bean直接進行互動時,語義會變的很奇怪。

組合生命週期機制

從Spring 2.5開始,在控制bean的生命週期行為時,你有三中選擇:InitializingBeanDisposableBean回撥介面;定製init()destroy()方法;@PostConstruct@PreDestroy`註解。在控制一個給定bean時你可以組合這些機制。

如果一個bean配置了多生命週期機制,每種機制配置了一個不同的方法名,那麼每一個配置的方法會按照下面的順序列表來執行。但是如果配置了相同的名字——例如,init()初始化方法——不止在一個生命週期機制中配置,那麼這個方法只能執行一次,像之前所說的那樣。

同一個bean配置了多生命週期機制,並有不同的初始化方法,那麼呼叫順序如下:

  • 先呼叫有註解@PostConstruct的方法

  • 然後呼叫InitializingBean回撥介面定義的afterPropertiesSet()方法

  • 最好呼叫定製配置的init()方法

Destroy methods are called in the same order:

  • Methods annotated with @PreDestroy

  • destroy() as defined by the DisposableBean callback interface

  • A custom configured destroy() method

析構方法按同樣的順序呼叫:

  • 先呼叫有@PreDestroy註解的方法

  • 再呼叫DisposableBean回撥介面定義的destroy()方法

  • 最好呼叫定製配置的destroy()方法

啟動和關閉回撥

Lifecycle介面定義了任何物件生命週期都需要的基本方法(例如啟動和停止一些背景處理):

public interface Lifecycle {

    void start();

    void stop();

    boolean isRunning();

}

任何Spring管理的物件都可以實現那個介面。當ApplicationContext本身收到啟動啟動和關閉訊號時,例如執行時關閉/再啟動場景,它將級聯呼叫所有的上下文定義的Lifecycle實現。它通過委託LifecycleProcessor來完成這個功能:

public interface LifecycleProcessor extends Lifecycle {

    void onRefresh();

    void onClose();

}

注意LifecycleProcessor本身是Lifecycle介面的一個擴充套件。它也新增了兩個其它的方法來響應上下文的再重新整理和關閉的。

注意正規的org.springframework.context.Lifecycle介面只是一個顯式啟動/關閉通知的協議,並不意味著在上下文重新整理時自動啟動。考慮實現org.springframework.context.SmartLifecycle介面來實現對指定bean自動啟動的細粒度控制(包括啟動時期)。請注意停止通知不能保證在銷燬之前到來:在正式關閉時,所有的Lifecycle beans在通常的析構回撥傳播之前首先會收到停止通知;但是,在上下文使用期間進行熱重新整理或嘗試取消再重新整理,只會呼叫析構方法。

啟動和關閉的呼叫順序是很重要的。如果任何兩個物件間存在一個"depends-on"關係,那麼依賴關係將在它的依賴之後開始,在它的依賴之前停止。然而有時直接的依賴關係是未知的。你可能只知道某個型別的物件應該在另一個型別的物件之前啟動。在那種情況下,SmartLifecycle介面定義了另一種選擇,也就是說getPhase()定義在它的父介面Phased中。

public interface Phased {
    int getPhase();
}
public interface SmartLifecycle extends Lifecycle, Phased {
    boolean isAutoStartup();
    void stop(Runnable callback);
}

當開始時,最低相位的物件先啟動,當停止時,最高相位的物件先停止。因此,實現了SmartLifecycle介面,getPhase()方法返回值為Integer.MIN_VALUE的物件將最先啟動並最後停止。另一方面,相位值Integer.MAX_VALUE表明物件應該最後啟動,最先停止(可能是因為它依賴其它執行的程式)。當考慮相位值時,知道任何沒有實現SmartLifecycle介面的Lifecycle物件的預設值為0是很重要的。因此,任何負相位值表示物件應該在那麼標準元件之前啟動(在它們之後停止),反之為任何正相位值。

正如你看到的,在SmartLifecycle中定義的停止方法接收一個回撥函式。任何實現在關閉程式完成之後都必須呼叫回撥的run()方法。當需要時這可以進行非同步關閉,因為LifecycleProcessor介面、DefaultLifecycleProcessor介面的預設實現會等待每個階段的物件組直到達到超時值,然後呼叫回撥函式。預設每個階段的超時值為30秒。你可以在上下文中通過定義名為"lifecycleProcessor"的bean來覆蓋預設的生命週期處理器例項。如果你只想修改超時值,如下定義是足夠的:

<bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor">
    <!-- timeout value in milliseconds -->
    <property name="timeoutPerShutdownPhase" value="10000"/>
</bean>

像上面提到的那樣,LifecycleProcessor介面為再重新整理和上下文的關閉也定義了回撥方法。後者會簡單的驅動關閉程式就像顯式的呼叫了stop()方法一樣,但當上下文關閉時它才會發生。另一方面refresh回撥能使SmartLifecycle beans的另一個功能可用。當上下文再重新整理時(所有物件已經例項化並初始化),回撥函式將被呼叫,那時預設的生命週期處理器將會檢查每個SmartLifecycle物件的isAutoStartup()方法返回的布林值。如果為true,物件將會在那時啟動而不是等待上下文的顯式呼叫或它自己的start()方法(不像上下文再重新整理,對於一個標準的上下文實現上下啟動不會自動發生)。"phase"值以及"depends-on"關係將決定啟動順序,像上面描述的一樣。

在非web應用中妥善的關閉Spring IoC容器

這一節只應用於非web應用。Spring的基於web的ApplicationContext實現已經有程式碼來處理當相關的web應用關閉時,妥善關閉Spring IoC容器的問題。

如果你在非web應用環境使用Spring的IoC容器;例如,在一個富桌面客戶端環境中,你在JVM中註冊一個關閉鉤子。這樣做確保了妥善的關閉,為了釋放所有資源需要呼叫與單例beans相關的析構方法。當然,你仍然必須正確的配置和實現這些銷燬回撥函式。

為了註冊一個關閉鉤子,你可以呼叫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...

    }
}

3.6.2 ApplicationContextAware和BeanNameAware

ApplicationContext建立一個實現org.springframework.context.ApplicationContextAware介面的物件例項時,這個例項會提供一個ApplicationContext的引用。

public interface ApplicationContextAware {

    void setApplicationContext(ApplicationContext applicationContext) throws BeansException;

}

因此beans可以以程式設計方式操縱建立它們的ApplicationContext,通過ApplicationContext介面,或通過將引用拋給這個介面的一個已知子類,例如ConfigurableApplicationContext,它暴露了額外的功能。一個方法是程式設計式檢索其他的bean。有時這個能力是很有用的,但是通常你應該避免使用它,因為它耦合了程式碼和Spring,不能遵循控制反轉的風格,在控制反轉中協作者是作為屬性提供給beans的。ApplicationContext的其它方法提供了對檔案資源的訪問,釋出應用事件,訪問MessageSource的功能。這些額外的特性將在3.15小節『ApplicationContext”的額外能力』中描述。

從Spring 2.5起,自動裝配是另一種可替代的獲得ApplicationContext引用的方法。『傳統的』constructorbyType自動裝配模式(如3.4.5小節所述,『自動裝配協作者』)可以分別為建構函式引數或setter方法引數提供ApplicationContext型別的依賴。更多的靈活性包括自動裝配變數的能力和多引數方法,使用新的基於註解的自動裝配特性。如果你這一做的話,ApplicationContext可以被自動裝配到變數中,建構函式引數中或方法引數中,如果討論的變數,建構函式或方法有@Autowired註解,那麼可以期望它是ApplicationContext型別。更多資訊請看3.9.2小節,@autowired

ApplicationContext建立一個實現了org.springframework.beans.factory.BeanNameAware介面的類時,類中有相關的物件定義中定義的名稱的引用。

public interface BeanNameAware {

    void setBeanName(String name) throws BeansException;

}

在正常的bean屬性填入之後,回撥方法呼叫,但在初始化回撥方法之前,例如InitializingBean的afterPropertiesSet或一個定製的初始化方法。

3.6.3 其它的Aware介面

除了上面討論的ApplicationContextAwareBeanNameAware之外,Spring給予了一系列Aware介面來允許beans向容器表明它們需要一個確定的基礎結構依賴。最重要的Aware介面總結如下——作為一個通用規則,名字是依賴型別的一個很好暗示:

表3.4. Aware介面

Name Injected Dependency Explained in
ApplicationContextAware 宣告ApplicationContext Section 3.6.2, “ApplicationContextAware and BeanNameAware”
ApplicationEventPublisherAware 封裝事件釋出的ApplicationContext Section 3.15, “Additional Capabilities of the ApplicationContext”
BeanClassLoaderAware 用來載入bean的類載入器 Section 3.3.2, “Instantiating beans”
BeanFactoryAware 宣告BeanFactory Section 3.6.2, “ApplicationContextAware and BeanNameAware”
BeanNameAware 宣告的bean的名字 Section 3.6.2, “ApplicationContextAware and BeanNameAware”
BootstrapContextAware 容器執行的資源自適應BootstrapContext. 通常只在JCA aware ApplicationContexts可獲得 Chapter 28, JCA CCI
LoadTimeWeaverAware 載入時為處理類定義定義的weaver Section 7.8.4, “Load-time weaving with AspectJ in the Spring Framework”
MessageSourceAware 解析訊息配置策略 (支援引數化和國際化) Section 3.15, “Additional Capabilities of the ApplicationContext”
NotificationPublisherAware Spring JMX通知釋出器 Section 27.7, “Notifications”
ResourceLoaderAware 為底層訪問資源配置的載入器 Chapter 4, Resources
ServletConfigAware 容器執行的當前ServletConfig。 僅在web感知的Spring ApplicationContext中有效 Chapter 18, Web MVC framework
ServletContextAware 容器執行的當前ServletContext。 僅在web感知的Spring ApplicationContext中有效 Chapter 18, Web MVC framework

注意這些介面的用法將你的程式碼與Spring進行了捆綁,不符合控制反轉的風格。因此,它們是為那麼需要以程式設計方式訪問容器的基礎結構beans推薦的。

相關文章