大數據

Spring 5 中文解析核心篇-IoC容器之自定義Bean性質

Spring框架提供一些接口,你可以使用這些接口去自定義bean的性質。這個章節包括下面內容:

  1. 生命週期回調
  2. ApplicationContextAwareBeanNameAwar
  3. 其他的Aware接口

1.6.1 生命週期回調

為了與容器的bean的生命週期的管理交互,你可以實現Spring提供的InitializingBeanDisposableBean接口。容器為前者調用afterPropertiesSet()併為後者調用destroy(),以使Bean在初始化和銷燬Bean時執行某些操作。

在現代化的Spring應用中,JSR-250@PostConstruct@PreDestroy註解是一般被考慮的最好實踐去接收生命週期回調。使用這些註解意味著你的這些bean不需要耦合Spring規範接口。更多詳情,查看@PostConstruct@PreDestroy

如果你不想去使用JSR-250註解,但是你仍然想移除耦合,考慮init-method和destroy-method bean的元數據定義。

內部地,Spring框架使用BeanPostProcessor實現去處理任何的回調接口,它能找到和調用適合的方法。如果你需要定製特性或者其他的生命週期行為Spring默認沒有提供,你需要自己實現一個BeanPostProcessor。關於更多的信息,查看容器擴展點

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

生命週期回調接口在這章節中描述。

  • 初始化回調

org.springframework.beans.factory.InitializingBean接口允許在bean的所有屬性被設置之前執行初始化工作。InitializingBean接口有一個簡單方法:

void afterPropertiesSet() throws Exception;

我們推薦你不要去使用InitializingBean接口,因為它不必要的耦合Spring。或者,我們建議使用@PostConstruct註解或者指定一個POJO初始化方法。在基於XML配置元數據實例中,你可以使用init-method屬性去指定方法的名稱並且方法沒有返回值和無參數簽名。在JavaConfig中,你可以使用@BeaninitMethod屬性。查看接收生命週期回調。考慮下面的例子:

<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 {

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

然而,前面兩個例子中的第一個沒有耦合Spring代碼。

參考代碼:com.liyong.ioccontainer.starter.XmlInitializationIocContainer

  • 銷燬回調

當包含bean的容器被銷燬時,實現org.springframework.beans.factory.DisposableBean接口的bean獲得回調。DisposableBean接口有個簡單方法:

void destroy() throws Exception;

我們推薦你不要使用DisposableBean回調接口,因為它不必要的耦合Spring代碼。或者,我們建議使用 @PreDestroy註解或者指定一個通用方法通過bean的定義支持。基於XML的配置元數據,你可以在上使用destroy-method屬性。通過JavaConfig配置,你可以使用@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 {

    @Override
    public void destroy() {
        // do some destruction work (like releasing pooled connections)
    }
}

然而,前面兩個定義中的第一個沒有耦合Spring代碼。

你可以賦值元素的destroy-method屬性值,這指示Spring自動的在bean類上檢測一個公共的close或者shutdown方法。(因此,任何實現java.lang.AutoCloseablejava.io.Closeable的類都將匹配)你可以在元素的default-destroy-method屬性設置這個指定的值去應用這個行為到整個bean容器(查看默認初始化和銷燬方法)。注意:這個一個默認行為在JavaConfig中。

參考代碼:com.liyong.ioccontainer.starter.XmlDestuctionIocContainer

  • 默認初始化和銷燬方法

當你寫初始化和銷燬方法回調的時候,不要使用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.");
        }
    }
}

然後,你可以在類似於以下內容的Bean中使用該類:

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

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

</beans>

頂層元素屬性default-init-method中描述導致Spring IoC容器在bean的類上去識別一個叫做init的方法作為初始化方法回調。當bean被創建和組裝好,如果bean的類有一個方法,它在適當的時候被調用。

你可以配置銷燬方法回調類似(在XML)的通過使用在頂層元素上的default-destroy-method方法。

現有的Bean類已經具有回調方法的名稱,這些方法的命名方式與約定不符,你可以通過元素屬性init-methoddestroy-method指定方法名稱。

Spring容器保證配置初始化方法在bean提供所有依賴後立即被調用。因此,在原始bean引用上調用初始化回調,這意味著AOP攔截器等還沒有應用到bean上。首先完全創建目標bean,然後應用帶有其攔截器鏈的AOP代理。如果目標Bean和代理分別定義,則你的代碼甚至可以繞過代理與原始目標Bean進行交互。因此,將攔截器應用於init方法是不一致的,因為這樣做會將目標bean的生命週期耦合到它的代理或攔截器,並在代碼直接與原始目標bean交互時留下奇怪的語義。

  • 組合生命週期機制

從Spring2.5後,你有三種可以選擇的方式去控制生命週期行為:

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

為同一個bean配置的多種生命週期機制具有不同的初始化方法,如下所示:

  1. 方法註解@PostConstruct
  2. 通過InitializingBean回調接口定義afterPropertiesSet()
  3. 自定義配置init() 方法

銷燬方法的調用順序相同:

  1. 方法註解@PreDestroy
  2. 通過DisposableBean回調接口定義destroy()
  3. 自定義配置destroy()方法
  • 啟動和關閉回調

Lifecycle接口定義了必須要的方法為這些bean生命週期所必須(例如:啟動和停止一些後臺處理)。

public interface Lifecycle {

    void start();

    void stop();

    boolean isRunning();
}

任何Spring管理的對象可能實現Lifecycle接口。然而,當ApplicationContext接收到啟動和停止信號(例如:在運行時停止和重啟場景),它將這些調用級聯到在該上下文中定義的所有生命週期實現。通過代理到LifecycleProcessor處理,在下面清單顯示:

public interface LifecycleProcessor extends Lifecycle {

    void onRefresh();

    void onClose();
}

注意:LifecycleProcessorLifecycle接口的拓展。這個接口增加了兩個額外的方法去接收上下文的刷新和關閉。

注意:

常規的org.springframework.context。生命週期接口是顯式啟動和停止通知的普通契約,並不意味著在上下文刷新時自動啟動。為了更細粒度控制bean的自動啟動(包括啟動階段),考慮使用org.springframework.context.SmartLifecycle替換。

停止通知不會被保證在銷燬之前到來。在常規的關閉上,所有Lifecyclebean第一次接受停止通知在銷燬回調被傳播之前。但是,在上下文生命週期中進行熱更新或更新嘗試失敗時,僅調用destroy方法。

啟動和關閉順序調用是非常重要的。如果依賴關係存在任何對象之間,依賴側開始在被依賴之後並且它的停止在它的依賴之前。然而,在運行時,這個直接依賴是未知的。你可能只知道某種類型的對象應該先於另一種類型的對象開始。在這種情況下,SmartLifecycle接口定義其他可選,即getPhase()方法在它的父接口被定義,Phased。下面的清單顯示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階段值指示這個對象最後一個被啟動並且第一個被銷燬 (可能因為它依賴其他處理運行)。當考慮這個階段值的時候,重要的是要知道,任何未實現SmartLifecycle的“正常”生命週期對象的默認階段為0。因此,任何負的階段值表示對象應該啟動在這些標準的組件之前(停止在它們之後)。對於任何正階段值,情況正好相反。

停止方法通過SmartLifecycle接受一個回調被定義。任何實現必須在實現的關閉處理完成後調用回調的run()方法。這將在必要時啟用異步關閉,因為LifecycleProcessor接口的默認實現DefaultLifecycleProcessor會等待其超時值,以等待每個階段內的對象組調用該回調。每個階段默認超時時間30秒。你可以通過定義一個bean 命名為lifecycleProcessor在上下文中覆蓋這個默認生命週期處理實例。如果你想僅僅修改超時時間,定義以下內容就足夠了:

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

像前面提到的,LifecycleProcessor接口定義回調方法為上下文更好的刷新和關閉。後者驅動關閉過程,就好像已經顯式調用了stop()一樣,但是它在上下文關閉時發生。另一方面,“refresh”回調啟用了SmartLifecycle bean的另一個特性。當這個上下文被刷新(所有的bean被初始化和實例化),即回調被調用。在那個階段,默認的生命週期處理器通過每個SmartLifecycle對象的isAutoStartup()方法檢測返回boolean值。如果true,對象在那個階段被啟動而不是等待上下文或者自身的start()方法顯示調用(與上下文刷新不同,對於標準上下文實現,上下文啟動不會自動發生)。像前面所描述的,phase值和任何依賴關係確定了啟動順序。

參考代碼:com.liyong.ioccontainer.starter.XmlLifecycleIocContainer

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

這個部分僅僅適用非web應用程序。Spring的基於WebApplicationContext實現提供優雅的關閉Spring IoC容器,當相關的web應用程序關閉的時候。

如果你使用SpringIoC容器在非web應用環境(例如,在一個富客戶端桌面環境),在JVM註冊一個關閉鉤子。這樣做確保優雅的關閉和調用在單例bean上的銷燬方法,因此所有資源被釋放。你必須仍然正確地配置和實現這些銷燬回調。

去註冊一個關閉鉤子,調用registerShutdownHook()方法是在ConfigurableApplicationContext接口上聲明,如下例子:

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");

        //增加一個Hook構造回調
        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創建它們,通過ApplicationContext接口或者轉換引用為已知的這個接口的子類(例如:ConfigurableApplicationContext,它暴露了附加的功能)。一種用途是通過編程方式檢索其他bean。有時候這個能力非常有用。然而,一般的,你應該避免它,因為它耦合Spring的代碼並且不遵循控制反轉格式,將協調者作為bean的屬性。ApplicationContext其他方法提供獲取資源文件、發佈應用事件和訪問MessageSource。這些額外的特性在ApplicationContext的附加能力中描述

自動裝配是獲得對ApplicationContext的引用的另一種選擇。傳統的constructorbyType自動裝配模式(在自動裝配協作器中描述)能夠為構造函數或者Setter方法參數提供一個ApplicationContext類型的依賴。為了獲得更大的靈活性,包括自動裝配字段和多個參數方法的能力,可以使用基於註解的自動裝配特性。如果這樣做,ApplicationContext自動裝配到字段、構造參數或方法參數,如果字段、構造函數或方法帶有@Autowired註解那麼該參數期望ApplicationContext類型。更多的信息,查看使用@Autowired

ApplicationContext創建一個類並且實現org.springframework.beans.factory.BeanNameAware接口時,該類將獲得對其關聯對象定義中定義的名稱的引用。下面清單顯示BeanNameAware接口定義:

public interface BeanNameAware {

    void setBeanName(String name) throws BeansException;
}

這個回調在bean被填充之後被調用,但是在初始化回調之前例如InitializingBeanafterPropertiesSet或者自定義init-method

參考代碼:com.liyong.ioccontainer.starter.XmlAwareIocContainer

1.6.3 其他Aware接口

除了ApplicationContextAwareBeanNameAware之外,Spring提供了廣泛的Aware回調接口,這些接口使Bean向容器指示它們需要一些基礎結構依賴性。作為基本規則,這個名字指示依賴類型。下面的表格總結最重要的Aware接口:

Name Injected Dependency Explained in…
ApplicationContextAware 注入 ApplicationContext. ApplicationContextAwareand BeanNameAware
ApplicationEventPublisherAware 注入ApplicationEventPublisher. Additional Capabilities of the ApplicationContext
BeanClassLoaderAware Class loader used to load the bean classes. Instantiating Beans
BeanFactoryAware 注入 BeanFactory. ApplicationContextAwareand BeanNameAware
BeanNameAware 注入BeanName ApplicationContextAwareand BeanNameAware
BootstrapContextAware 注入BootstrapContext JCA CCI
LoadTimeWeaverAware 注入LoadTimeWeaver. Load-time Weaving with AspectJ in the Spring Framework
MessageSourceAware 注入MessageSource Additional Capabilities of the ApplicationContext
NotificationPublisherAware 注入NotificationPublisher. Notifications
ResourceLoaderAware 注入ResourceLoader Resources
ServletConfigAware 注入ServletConfig. Spring MVC
ServletContextAware 注入ServletContext. Spring MVC

再次注意,使用這些接口關聯到你的代碼到Spring API並且不遵循控制反轉的風格。我們推薦使用基礎設施bean需要編程的去容器獲取。

作者

個人從事金融行業,就職過易極付、思建科技、某網約車平臺等重慶一流技術團隊,目前就職於某銀行負責統一支付系統建設。自身對金融行業有強烈的愛好。同時也實踐大數據、數據存儲、自動化集成和部署、分佈式微服務、響應式編程、人工智能等領域。同時也熱衷於技術分享創立公眾號和博客站點對知識體系進行分享。

博客地址: http://youngitman.tech

CSDN: https://blog.csdn.net/liyong1028826685

微信公眾號:

技術交流群:

Leave a Reply

Your email address will not be published. Required fields are marked *