八仙過海,各顯神通
Spring 是一個控制反轉依賴管理的容器,作為 Java Web 的開發人員,基本沒有不熟悉 Spring 技術棧的,盡管在依賴注入領域,Java Web 領域不乏其他優秀的架構,如 google 開源的依賴管理架構 guice,如 Jersey web 架構等。但 Spring 已經是 Java Web 領域使用最多,應用最廣泛的 Java 架構。
此文将專注講解如何在 Spring 容器啟動時實作我們自己想要實作的邏輯。我們時常會遇到在 Spring 啟動的時候必須完成一些初始化的操作,如建立定時任務,建立連接配接池等。
本文将介紹以下幾種 Spring 啟動監聽方式:
Bean 構造函數方式
使用 @PostConstruct 注解
實作 InitializingBean 接口
監聽 ApplicationListener 事件
使用 Constructor 注入方式
實作 SpringBoot 的 CommandLineRunner 接口
SmartLifecycle 機制
如果沒有 Spring 容器,不依賴于 Spring 的實作,回歸 Java 類實作本身,我們可以在靜态代碼塊,在類構造函數中實作相應的邏輯,Java 類的初始化順序依次是<code>靜态變量</code> > <code>靜态代碼塊</code> > <code>全局變量</code> > <code>初始化代碼塊</code> > <code>構造器</code>。
比如,Log4j 的初始化,就是在 <code>LogManager</code> 的靜态代碼塊中實作的:
比如在構造函數中實作相應的邏輯:
這裡考驗一下各位,上面的代碼是否可以正常運作。—— 不行,構造函數中的<code>env</code>将會發生<code>NullPointException</code>異常。這是因為在 Spring 中将先初始化 Bean,也就是會先調用類的構造函數,然後才注入成員變量依賴的 Bean(<code>@Autowired</code>和<code>@Resource</code>注解修飾的成員變量),注意<code>@Value</code>等注解的配置的注入也是在構造函數之後。
在 Spring 中,我們可以使用<code>@PostConstruct</code>在 Bean 初始化之後實作相應的初始化邏輯,<code>@PostConstruct</code>修飾的方法将在 Bean 初始化完成之後執行,此時 Bean 的依賴也已經注入完成,是以可以在方法中調用注入的依賴 Bean。
與<code>@PostConstruct</code>相對應的,如果想在 Bean 登出時完成一些清掃工作,如關閉線程池等,可以使用<code>@PreDestroy</code>注解:
實作 Spring 的<code>InitializingBean</code>接口同樣可以實作以上在 Bean 初始化完成之後執行相應邏輯的功能,實作<code>InitializingBean</code>接口,在<code>afterPropertiesSet</code>方法中實作邏輯:
我們可以在 Spring 容器初始化的時候實作我們想要的初始化邏輯。這時我們就可以使用到 Spring 的初始化事件。Spring 有一套完整的事件機制,在 Spring 啟動的時候,Spring 容器本身預設了很多事件,在 Spring 初始化的整個過程中在相應的節點觸發相應的事件,我們可以通過監聽這些事件來實作我們的初始化邏輯。Spring 的事件實作如下:
ApplicationEvent,事件對象,由 ApplicationContext 釋出,不同的實作類代表不同的事件類型。
ApplicationListener,監聽對象,任何實作了此接口的 Bean 都會收到相應的事件通知。實作了 ApplicationListener 接口之後,需要實作方法 onApplicationEvent(),在容器将所有的 Bean 都初始化完成之後,就會執行該方法。
與 Spring Context 生命周期相關的幾個事件有以下幾個:
ApplicationStartingEvent: 這個事件在 Spring Boot 應用運作開始時,且進行任何處理之前發送(除了監聽器和初始化器注冊之外)。
ContextRefreshedEvent: ApplicationContext 被初始化或重新整理時,該事件被釋出。這也可以在 ConfigurableApplicationContext 接口中使用 refresh() 方法來發生。
ContextStartedEvent: 當使用 ConfigurableApplicationContext 接口中的 start() 方法啟動 ApplicationContext 時,該事件被觸發。你可以查詢你的資料庫,或者你可以在接受到這個事件後重新開機任何停止的應用程式。
ApplicationReadyEvent: 這個事件在任何 application/ command-line runners 調用之後發送。
ContextClosedEvent: 當使用 ConfigurableApplicationContext 接口中的 close() 方法關閉 ApplicationContext 時,該事件被觸發。一個已關閉的上下文到達生命周期末端;它不能被重新整理或重新開機。
ContextStoppedEvent: Spring 最後完成的事件。
是以,如果我們想在 Spring 啟動的時候實作一些相應的邏輯,可以找到 Spring 啟動過程中符合我們需要的事件,通過監聽相應的事件來完成我們的邏輯:
除了通過實作<code>ApplicationListener</code>接口來監聽相應的事件,Spring 的事件機制也實作了通過<code>@EventListener</code>注解來監聽相對應事件:
Spring Event 是一套完善的程序内事件釋出訂閱機制,我們除了用來監聽 Spring 内置的事件,也可以使用 Spring Event 實作自定義的事件釋出訂閱功能。
在學習 Spring 的注入機制的時候,我們都知道 Spring 可以通過構造函數、Setter 和反射成員變量注入等方式。上面我們在成員變量上通過<code>@Autoware</code>注解注入依賴 Bean,但是在 Bean 的構造函數函數中卻無法使用到注入的 Bean(因為 Bean 還未注入),其實我們也是使用 Spring 的構造函數注入方式, 這也是 Spring 推薦的注入機制(在我們使用 IDEA 的時候,如果沒有關閉相應的代碼 Warning 機制,會發現在成員變量上的<code>@Autoware</code>是黃色的,也就是 idea 不建議的代碼)。Spring 更推薦構造函數注入的方式:
如果我們的項目使用的是 Spring Boot,那麼可以使用 Spring Boot 提供的<code>CommandLineRunner</code> 接口來實作初始化邏輯,Spring Boot 将在啟動初始化完成之後調用實作了<code>CommandLineRunner</code>的接口的<code>run</code>方法:
并且,多個<code>CommandLineRunner</code>實作,可以通過<code>@Order</code>來控制它們的執行順序。
還有一種更進階的方法來實作我們的邏輯。這可以 Spring 進階開發必備技能哦。SmartLifecycle 不僅僅能在初始化後執行一個邏輯,還能再關閉前執行一個邏輯,并且也可以控制多個 <code>SmartLifecycle</code> 的執行順序,就像這個類名表示的一樣,這是一個智能的生命周期管理接口。
start():bean 初始化完畢後,該方法會被執行。
stop():容器關閉後,spring 容器發現目前對象實作了 SmartLifecycle,就調用 stop(Runnable), 如果隻是實作了 Lifecycle,就調用 stop()。
isRunning:目前狀态,用來判你的斷元件是否在運作。
getPhase:控制多個 SmartLifecycle 的回調順序的,傳回值越小越靠前執行 start() 方法,越靠後執行 stop() 方法。
isAutoStartup():start 方法被執行前先看此方法傳回值,傳回 false 就不執行 start 方法了。
stop(Runnable):容器關閉後,spring 容器發現目前對象實作了 SmartLifecycle,就調用 stop(Runnable), 如果隻是實作了 Lifecycle,就調用 stop()。
https://mp.weixin.qq.com/s/4dWKzJRyiv94H035Lwmokg