Spring 核心技術(7)


接上篇:Spring 核心技術(6)

version 5.1.8.RELEASE

1.6 定制 Bean 的特性

Spring Framework 提供了許多可用於自定義 bean 特性的接口。本節將它們分組如下:

1.6.1 生命周期回調

要與容器的 bean 生命周期管理進行交互,可以實現 Spring InitializingBeanDisposableBean 接口。容器調用前者 afterPropertiesSet() 和后者的 destroy() 以便讓 bean 在初始化和銷毀 ​​bean 時執行某些操作。

JSR-250 @PostConstruct@PreDestroy 注解通常被認為是在現代 Spring 應用程序中接收生命周期回調的最佳實踐。使用這些注解意味着你的 bean 不會耦合到特定的 Spring 接口。有關詳細信息,請參閱使用 @PostConstruct 和 @PreDestroy

在內部,Spring Framework 使用 BeanPostProcessor 實現來處理它可以找到的任何回調接口並調用適當的方法。如果你需要自定義其他 Spring 默認不提供的功能或生命周期行為,可以自己實現 BeanPostProcessor。更多信息請參閱容器擴展點

除了初始化和銷毀​​回調之外,Spring 管理的對象還可以實現 Lifecycle 接口,以便這些對象可以參與容器自身的生命周期驅動的啟動和關閉過程。

本節描述了生命周期回調接口。

初始化回調

org.springframework.beans.factory.InitializingBean 接口允許 bean 在容器完全設置其所有必要屬性后進行初始化工作。InitializingBean 接口規定了一個方法:

void afterPropertiesSet() throws Exception;

我們建議不要使用 InitializingBean 接口,因為並不需要將代碼耦合到 Spring。此外,我們建議使用 @PostConstruct 注解或指定 POJO 初始化方法。對於基於 XML 的配置元數據,可以使用 init-method 屬性指定具有無返回值無參數簽名的方法的名稱。使用 Java 配置時,可以使用 @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。

銷毀回調

實現 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。

你可以為 <bean> 元素的 destroy-method 屬性指定一個特殊(推測)值,該值指示 Spring 自動檢測特定 bean 類的公共 closeshutdown 方法。(任何實現 java.lang.AutoCloseable或java.io.Closeable 的類都可以進行匹配。)你還可以在 <beans> 元素的 default-destroy-method 屬性上設置此特殊(推測)值,以將此行為應用於整組 bean(請參閱默認初始化和銷毀​​方法)。請注意,這是使用 Java 配置時的默認行為。

默認初始化和銷毀​​方法

當你不使用 Spring 特定的 InitializingBeanDisposableBean 回調接口編寫初始化和銷毀回調方法時,通常會使用 init()initialize()dispose() 等來命名方法。理想情況下,此類生命周期回調方法的名稱在項目中是一致的的,以便所有開發人員使用相同的方法名稱並確保一致性。

你可以將Spring容器配置為在每個 bean 上“查找”已經命名的初始化和銷毀回調方法的名稱。這意味着,作為應用程序開發人員,你可以編寫應用程序類並使用 init() 作為初始化回調 ,而無需為每個 bean 定義配置 init-method="init" 屬性。Spring IoC 容器在創建 bean 時調用該方法(根據前面描述的標准生命周期回調)。此功能還強制執行初始化和銷毀​​方法回調的一致命名約定。

假設你的初始化回調方法已命名為 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.");
        }
    }
}

你可以在類似於以下內容的 bean 中使用該類:

<beans default-init-method="init">

    <bean id="blogService" class="com.something.DefaultBlogService">
        <property name="blogDao" ref="blogDao" />
    </bean>

</beans>

頂級 <beans/> 元素上存在 default-init-method 屬性導致 Spring IoC 容器將 bean 類上叫做 init 的方法識別為初始化方法回調。當 bean 被創建和組裝時,如果 bean 類具有這樣的方法,則會在適當的時候調用它。

你可以在XML中同樣通過使用頂級 <beans/> 元素上的 default-destroy-method 屬性來配置銷毀方法回調。

如果現有的 bean 類已經具有與約定一致的回調方法,則可以在 XML 中通過使用自身 <bean/>init-methoddestroy-method 屬性指定方法名稱來覆蓋默認值。

Spring 容器可以保證在為 bean 提供所有依賴項后立即調用已配置的初始化回調。因此,初始化回調實在原始 bean 引用上調用的,這意味着 AOP 攔截器等尚未應用於 bean。首先完全創建目標 bean,然后應用的 AOP 代理(例如帶有攔截器鏈)。如果目標 bean 和代理是分開定義的,那么你的代碼甚至可以繞過代理與原始目標 bean 交互。因此,將攔截器應用於 init 方法是不合適的,因為這樣做會將目標 bean 的生命周期耦合到其代理或攔截器,並在代碼直接與原始目標 bean 交互時表現出奇怪的語義。

合並生命周期機制

從 Spring 2.5 開始,你有三個控制 bean 生命周期行為的選項:

如果為 bean 配置了多個生命周期機制,並且每個機制都配置了不同的方法名稱,則每個配置的方法都按照此注釋后列出的順序執行。但是,如果為多個這些生命周期機制配置了相同的方法名稱(例如,給初始化方法命名為 init()),如上 一節中所述,該方法將執行一次。

為同一個 bean 配置的多個不同的初始化方法時,執行順序如下:

  • @PostConstruct 注解的方法
  • InitializingBean 定義的 afterPropertiesSet() 回調接口
  • 自定義配置的 init() 方法

銷毀方法以相同的順序調用:

  • @PreDestroy 注解的方法
  • DisposableBean 定義的 destroy() 回調接口
  • 自定義配置的 destroy() 方法

啟動和關閉回調

Lifecycle 接口為任何具有自己的生命周期要求的對象(例如啟動和停止某些后台進程)定義了基本方法:

public interface Lifecycle {

    void start();

    void stop();

    boolean isRunning();
}

任何 Spring 管理的對象都可以實現 Lifecycle 接口。然后,當 ApplicationContext 接收到啟動和停止信號時(例如,對於運行時的停止/重啟場景),它將級聯調用上下文中定義的 Lifecycle 的所有實現。它通過委托給 LifecycleProcessor 來實現,如下面的清單所示:

public interface LifecycleProcessor extends Lifecycle {

    void onRefresh();

    void onClose();
}

請注意,LifecycleProcessor 它本身是 Lifecycle 接口的擴展。它還添加了另外兩種方法來響應刷新和關閉上下文。

請注意,常規的 org.springframework.context.Lifecycle 接口是顯式啟動和停止通知的簡單協議,並不意味着在上下文刷新時自動啟動。要對特定bean的自動啟動(包括啟動階段)進行細粒度控制,請考慮實現 org.springframework.context.SmartLifecycle
此外,請注意,在銷毀之前不保證能收到停止通知。在常規關閉時,所有 Lifecycle bean 在一般銷毀回調開始之前首先收到停止通知。但是,在上下文生命周期中的熱刷新或中止刷新嘗試時,僅調用銷毀方法。

啟動和關閉調用的順序非常重要。如果任何兩個對象之間存在“依賴”關系,則依賴方在其依賴之后開始,並且在其依賴之前停止。但是,有時,直接依賴性是未知的。你可能只知道某種類型的對象應該在另一種類型的對象之前開始。在這些情況下,SmartLifecycle 接口定義了另一個選項,即在其父級接口 Phased 上定義的方法 getPhase() 。以下內容展示了 Phased 接口的定義:

public interface Phased {

    int getPhase();
}

以下內容展示了 SmartLifecycle 接口的定義:

public interface SmartLifecycle extends Lifecycle, Phased {

    boolean isAutoStartup();

    void stop(Runnable callback);
}

啟動時,具有最低層的對象首先開始。停止時,遵循相反的順序。因此,實現 SmartLifecycle 的對象和 getPhase() 返回的 Integer.MIN_VALUE 將是第一個開始和最后一個停止的對象。在另外一個領域內,相位值 Integer.MAX_VALUE 將指示對象應該最后啟動並首先停止(可能因為它依賴於正在運行的其他進程)。當考慮相位值,同樣重要的是要知道,對於任何“正常”的沒有實現 SmartLifecycleLifecycle 對象,默認值為 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 bean的另一個功能 。刷新上下文時(在實例化並初始化所有對象之后),將調用該回調。此時,默認生命周期處理器檢查每個 SmartLifecycle 對象的 isAutoStartup() 方法返回的布爾值 。如果是 true,那么對象是當時就開始的,而不是等待顯式調用上下文或它自己的 start() 方法(與上下文刷新不同,上下文啟動不會自動發生在標准上下文實現中)。phase 值與任何“依賴式”的關系確定了前面所述的啟動順序。

在非 Web 應用程序中優雅地關閉 Spring IoC 容器

本節僅適用於非 Web 應用程序。Spring 的基於 Web 的 ApplicationContext 實現已經具有相關的代碼,可以在相關 Web 應用程序關閉時正常關閉 Spring IoC 容器。

如果在非 Web 應用程序環境中使用 Spring 的 IoC 容器(例如,在客戶機桌面環境中),請使用 JVM 注冊關閉鈎子。這樣做可確保正常關閉並在單例 bean 上調用相關的銷毀方法,以便釋放所有資源。你必須正確配置和實現這些銷毀回調。

要注冊關閉鈎子,請調用 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("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...
    }
}

1.6.2 ApplicationContextAwareBeanNameAware

ApplicationContext 創建實現 org.springframework.context.ApplicationContextAware 接口的對象實例時,將為 ApplicationContext 提供對該實例的引用。以下清單顯示了 ApplicationContextAware 接口的定義:

public interface ApplicationContextAware {

    void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}

因此,bean 可以通過 ApplicationContext 接口或通過將引用轉換為此接口的已知子類(例如公開其他功能的 ConfigurableApplicationContext,)以編程方式操縱創建它們的 ApplicationContext。一種用途是對其他 bean 進行編程檢索。有時這種功能很有用。但是,一般情況下應該避免使用它,因為協作者作為屬性提供給 bean 時會將代碼耦合到 Spring 並且不遵循控制反轉風格。ApplicationContext 的其他方法提供對文件資源的訪問,發布應用程序事件以及訪問 MessageSource。這些附加功能在ApplicationContext 的附加功能中描述 。

從 Spring 2.5 開始,自動裝配是另一種獲取 ApplicationContext 引用的方法。“傳統” constructorbyType 自動裝配模式(如自動裝配協作者中所述)可以分別為構造函數參數或 setter 方法參數提供 ApplicationContext 類型的依賴。為了獲得更大的靈活性,以及自動裝配字段和多參數方法的能力,請使用基於注釋的新自動裝配功能。如果相關的字段,構造函數或方法帶有 @Autowired 注解且需要 ApplicationContext 類型,ApplicationContext 會自動裝入一個字段,構造函數參數或方法參數。有關更多信息,請參閱使用 @Autowired

ApplicationContext 創建實現 org.springframework.beans.factory.BeanNameAware 接口的類時,將為該類提供其關聯對象定義中對應名稱的引用。以下清單顯示了 BeanNameAware 接口的定義:

public interface BeanNameAware {

    void setBeanName(String name) throws BeansException;
}

回調在普通 bean 屬性設置之后,但在一個初始化回調之前,例如 InitializingBeanafterPropertiesSet 或自定義的初始化方法。

1.6.3 其他 Aware 接口

除了 ApplicationContextAwareBeanNameAware (在之前討論過),Spring 提供了廣泛的 Aware 回調讓 bean 向容器指出他們需要一定的基礎設依賴。作為一般規則,名稱表示依賴關系類型。下表總結了最重要的 Aware 接口:

名稱 注入依賴 描述
ApplicationContextAware 聲明 ApplicationContext ApplicationContextAwareBeanNameAware
ApplicationEventPublisherAware 包含 ApplicationContext 的時間發布者 ApplicationContext 的其他功能
BeanClassLoaderAware 用於加載bean類的類加載器 實例化 Bean
BeanFactoryAware 聲明 BeanFactory ApplicationContextAwareBeanNameAware
BeanNameAware 聲明 bean 的名稱 ApplicationContextAwareBeanNameAware
BootstrapContextAware 容器運行的資源適配器 BootstrapContext。通常僅在支持 JCA 的 ApplicationContext 實例中可用 JCA CCI
LoadTimeWeaverAware 定義在加載時用於處理類定義的 weaver 在 Spring 框架中使用 AspectJ 進行加載時織入
MessageSourceAware 用於解析消息的已配置策略(支持參數化和國際化) ApplicationContext 的其他功能
NotificationPublisherAware Spring JMX 通知發布者 通知
ResourceLoaderAware 用於對低層次資源進行訪問的配置加載器 資源
ServletConfigAware 當前容器運行的 ServletConfig。僅在支持 Web 的 ApplicationContext 中有效 Spring MVC
ServletContextAware 當前容器運行的 ServletContext。僅在支持 Web 的 ApplicationContext 中有效 Spring MVC

請再次注意,使用這些接口會使你的代碼與 Spring API 耦合,而且不會遵循 IOC 規范。因此,我們建議將它們用於需要以編程方式訪問容器的基礎架構 bean。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM