1. 程式人生 > >Spring 核心技術(7)

Spring 核心技術(7)

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

version 5.1.8.RELEASE

1.6 定製 Bean 的特性

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

  • 生命週期回撥
  • ApplicationContextAwareBeanNameAware
  • 其他 Aware 介面

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 生命週期行為的選項:

  • 在 InitializingBean和 DisposableBean 回撥介面
  • 自定義 init()destroy() 方法
  • @PostConstruct@PreDestroy 註解。你可以組合這些機制來控制指定的 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。

  • 我的CSDN:https://blog.csdn.net/liweitao7610
  • 我的部落格園:https://www.cnblogs.com/aotian/
  • 我的簡書:https://www.jianshu.com/u/6b6e162f1fdc