前言
在我們用springboot搭建專案的時候,有時候會碰到在專案啟動時初始化一些操作的需求,針對這種需求springboot(spring)為我們提供了以下幾種方案供我們選擇:
ApplicationRunner
與CommandLineRunner
介面- Spring Bean初始化的InitializingBean,init-method和PostConstruct
- Spring的事件機制
ApplicationRunner與CommandLineRunner
如果需要在SpringApplication
啟動時執行一些特殊的程式碼,你可以實現ApplicationRunner
或CommandLineRunner
介面,這兩個介面工作方式相同,都只提供單一的run方法,而且該方法僅在SpringApplication.run(...)完成之前呼叫,更準確的說是在構造SpringApplication例項完成之後呼叫run()的時候,具體分析見後文,所以這裡將他們分為一類。
ApplicationRunner
構造一個類實現ApplicationRunner介面
@Component
public class ApplicationRunnerTest implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("ApplicationRunner");
}
}
複製程式碼
專案啟動測試結果如圖:
CommandLineRunner
對於這兩個介面而言,我們可以通過Order註解或者使用Ordered介面來指定呼叫順序,@Order()
中的值越小,優先順序越高
@Component
@Order(1)
public class CommandLineRunnerTest implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
System.out.println("CommandLineRunner...");
}
}
複製程式碼
當然我們也可以同時使用ApplicationRunner
和CommandLineRunner
,預設情況下前者比後者先執行,但是這沒有必要,使用一個就好了
兩者的聯絡與區別
前面就提到過,兩個介面都有run()方法,只不過它們的引數不一樣,CommandLineRunner的引數是最原始的引數,沒有進行任何處理,ApplicationRunner的引數是ApplicationArguments,是對原始引數的進一步封裝
原始碼跟蹤
接下來我們簡要跟蹤一下原始碼看ApplicationRunner(CommandLineRunner)
是如何被呼叫的。
Springboot在啟動的時候,都會構造一個SpringApplication
例項,至於這個例項怎麼構造的,這裡不去探究了,有感興趣的可以去看下原始碼。這裡主要看ApplicationRunner
是如何被呼叫的,而它的呼叫就是在SpringApplication
這個例項呼叫run方法中。
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
複製程式碼
進入run方法
public static ConfigurableApplicationContext run(Class<?> primarySource,
String... args) {
return run(new Class<?>[] { primarySource }, args);
}
複製程式碼
執行SpringApplication的run方法
public static ConfigurableApplicationContext run(Class<?>[] primarySources,
String[] args) {
return new SpringApplication(primarySources).run(args);
}
複製程式碼
一路點選run()來到這裡
發現對ApplicationRunner的呼叫實際上在callRunners方法中 對於CommandLineRunner或者ApplicationRunner來說,需要注意的兩點:- 所有CommandLineRunner/ApplicationRunner的執行時點是在SpringBoot應用的ApplicationContext完全初始化開始工作之後,
callRunners()
可以看出是run方法內部最後一個呼叫的方法(可以認為是main方法執行完成之前最後一步) - 只要存在於當前SpringBoot應用的ApplicationContext中的任何CommandLineRunner/ApplicationRunner,都會被載入執行(不管你是手動註冊還是自動掃描去Ioc容器)
Spring Bean初始化的InitializingBean,init-method和PostConstruct
InitializingBean介面
InitializingBean
介面為bean
提供了初始化方法的方式,它只包括afterPropertiesSet()
方法。
在spring初始化bean的時候,如果bean實現了InitializingBean
介面,在物件的所有屬性被初始化後之後才會呼叫afterPropertiesSet()方法
@Component
public class InitialingzingBeanTest implements InitializingBean {
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("InitializingBean..");
}
}
複製程式碼
當然,我們可以看出spring初始化bean肯定會在 ApplicationRunner和CommandLineRunner介面呼叫之前。
當然有一點我們要注意的是,儘管使用initialingBean
介面可以實現初始化動作,但是官方並不建議我們使用InitializingBean
介面,因為它將你的程式碼耦合在Spring程式碼中,官方的建議是在bean的配置檔案指定init-method
方法,或者在@Bean
中設定init-method
屬性
init-method和@PostConstruct
前面就說過官方文件上不建議使用InitializingBean
介面,但是我們可以在<bean>
元素的init-method屬性指定bean初始化之後的操作方法,或者在指定方法上加上@PostConstruct註解來制定該方法在初始化之後呼叫
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@PostConstruct
public void init() {
System.out.println("init...");
}
}
複製程式碼
更多關於Spring Bean的生命週期的內容,請參閱Spring相關書籍或部落格Spring Bean的生命週期
Spring的事件機制
Spring的事件機制實際上是設計模式中觀察者模式的典型應用,在Head First 設計模式中是這樣定義觀察者模式的:
觀察者模式定義了一個一對多的依賴關係,讓一個或多個觀察者物件監聽一個主題物件。這樣一來,當被觀察者狀態改變時,需要通知相應的觀察者,使這些觀察者能夠自動更新
基礎概念
Spring的事件驅動模型由三部分組成
- 事件:
ApplicationEvent
,繼承自JDK的EventObject
,所有事件都要繼承它,也就是被觀察者 - 事件釋出者:
ApplicationEventPublisher
及ApplicationEventMulticaster
介面,使用這個介面,就可以釋出事件了 - 事件監聽者:
ApplicationListener
,繼承JDK的EventListener
,所有監聽者都繼承它,也就是我們所說的觀察者,當然我們也可以使用註解@EventListener
,效果是一樣的
事件
在Spring框架中,預設對ApplicationEvent事件提供瞭如下支援:
- ContextStartedEvent:ApplicationContext啟動後觸發的事件
- ContextStoppedEvent:ApplicationContext停止後觸發的事件
- ContextRefreshedEvent:ApplicationContext初始化或重新整理完成後觸發的事件;(容器初始化完成後呼叫,所以我們可以利用這個事件做一些初始化操作)
- ContextClosedEvent:ApplicationContext關閉後觸發的事件;(如web容器關閉時自動會觸發spring容器的關閉,如果是普通java應用,需要呼叫ctx.registerShutdownHook();註冊虛擬機器關閉時的鉤子才行)
構造一個類繼承ApplicationEvent
public class TestEvent extends ApplicationEvent {
private static final long serialVersionUID = -376299954511699499L;
private String message;
/**
* Create a new ApplicationEvent.
*
* @param source the object on which the event initially occurred (never {@code null})
*/
public TestEvent(Object source) {
super(source);
}
public void getMessage() {
System.out.println(message);
}
public void setMessage(String message) {
this.message = message;
}
}
複製程式碼
建立事件監聽者
有兩種方法可以建立監聽者,一種是直接實現ApplicationListener的介面,一種是使用註解 @EventListener
,註解是新增在監聽方法上的,下面的例子是直接實現的介面
@Component
public class ApplicationListenerTest implements ApplicationListener<TestEvent> {
@Override
public void onApplicationEvent(TestEvent testEvent)
{
testEvent.getMessage();
}
}
複製程式碼
事件釋出
對於事件釋出,代表者是ApplicationEventPublisher
和ApplicationEventMulticaster
,系統提供的實現如下
下面是一個事件釋出者的測試例項:
@RunWith(SpringRunner.class)
@SpringBootTest
public class EventTest {
@Autowired
private ApplicationContext applicationContext;
@Test
public void publishTest() {
TestEvent testEvent = new TestEvent("");
testEvent.setMessage("hello world");
applicationContext.publishEvent(testEvent);
}
}
//output:
hello world
複製程式碼
利用ContextRefreshedEvent事件進行初始化操作
前面做了這麼多鋪墊,下面進入今天的主題,利用Spring的事件機制進行初始化一些操作,實際上就是前面提到了,利用ContextRefreshedEvent
事件進行初始化,該事件是ApplicationContext
初始化完成後呼叫的事件,所以我們可以利用這個事件,對應實現一個監聽器,在其onApplicationEvent()
方法裡初始化操作
@Component
public class ApplicationListenerTest implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
System.out.println("我被呼叫了..");
}
}
複製程式碼
注意: 在傳統的基於XML配置的Spring專案中會存在二次呼叫的問題,即呼叫兩次該方法,原因是在傳統的Spring MVC專案中,系統存在兩個容器,一個root容器,一個project-servlet.xml對應的子容器,在初始化這兩個容器的時候都會呼叫該方法一次,所以有二次呼叫的問題,而對於基於Springboot的專案不存在這個問題
小結
以上簡要總結了在springboot啟動時進行初始化操作的幾個方案,這幾種方式都可以滿足我們的需求,針對具體場景使用對應的方案。但是,CommandLineRunner或者ApplicationRunner不是Spring框架原有的東西,它倆屬於SpringBoot應用特定的回撥擴充套件介面,所以很容易進行擴充套件,在一些微服務應用中使用也較廣泛。