SpringBoot 啟動時初始化數(shù)據(jù)
0x01:前言
在使用 springboot 搭建項目的時候,有時候會碰到在項目啟動時初始化一些操作的需求,針對這種需求 springboot(spring) 提供了以下幾種方案:
ApplicationRunner 與 CommandLineRunner 接口
Spring Bean 初始化的I nitializingBean, init-method 和 PostConstruct
Spring 的事件機(jī)制
0x02:ApplicationRunner與CommandLineRunner
如果需要在SpringApplication啟動時執(zhí)行一些特殊的代碼,可以實現(xiàn)ApplicationRunner 或 CommandLineRunner 接口,這兩個接口工作方式相同,都只提供單一的 run() 方法,而且該方法僅在SpringApplication.run(…)完成之前調(diào)用,更準(zhǔn)確的說是在構(gòu)造 SpringApplication 實例完成之后調(diào)用 run() 的時候,具體分析見后文,所以這里將他們分為一類。
ApplicationRunner
構(gòu)造一個類實現(xiàn)ApplicationRunner接口
@Component
public class ApplicationRunnerTest implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("ApplicationRunner");
}
}CommandLineRunner
對于這兩個接口而言,可以通過 Order 注解或者使用 Ordered 接口來指定調(diào)用順序,@Order() 中的值越小,優(yōu)先級越高
@Component
@Order(1)
public class CommandLineRunnerTest implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
System.out.println("CommandLineRunner...");
}
}當(dāng)然也可以同時使用 ApplicationRunner 和 CommandLineRunner,默認(rèn)情況下前者比后者先執(zhí)行,但是這沒有必要,使用一個就可以了。
兩者的聯(lián)系與區(qū)別
這兩個接口都有 run() 方法,只不過它們的參數(shù)不一樣,CommandLineRunne r的參數(shù)是最原始的參數(shù),沒有進(jìn)行任何處理,ApplicationRunner 的參數(shù)是ApplicationArguments, 是對原始參數(shù)的進(jìn)一步封裝。
簡要跟蹤一下源碼看 ApplicationRunner(CommandLineRunner) 是如何被調(diào)用的。
Springboot 在啟動的時候,都會構(gòu)造一個 SpringApplication 實例,至于這個實例怎么構(gòu)造的,這里不去探究了,有感興趣的可以去看下源碼。這里主要看ApplicationRunner 是如何被調(diào)用的,而它的調(diào)用就是在SpringApplication這個實例調(diào)用run方法中。
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}進(jìn)入run方法
public static ConfigurableApplicationContext run(Class<?> primarySource,
String... args) {
return run(new Class<?>[] { primarySource }, args);
}執(zhí)行 SpringApplication 的 run 方法
public static ConfigurableApplicationContext run(Class<?>[] primarySources,
String[] args) {
return new SpringApplication(primarySources).run(args);
}一路點擊 run() 來,發(fā)現(xiàn)對 ApplicationRunner 的調(diào)用實際上在 callRunners 方法中
對于 CommandLineRunner 或者 ApplicationRunner 來說,需要注意的兩點:
所有 CommandLineRunner / ApplicationRunner 的執(zhí)行時點是在 SpringBoot 應(yīng)用的 ApplicationContext 完全初始化開始工作之后,callRunners() 可以看出是 run 方法內(nèi)部最后一個調(diào)用的方法(可以認(rèn)為是main方法執(zhí)行完成之前最后一步)
只要存在于當(dāng)前 SpringBoot 應(yīng)用的 ApplicationContext 中的任何CommandLineRunner / ApplicationRunner,都會被加載執(zhí)行(不管你是手動注冊還是自動掃描去Ioc容器)
0x03:Spring Bean初始化的InitializingBean,init-method和PostConstruct
InitializingBean接口
InitializingBean 接口為 bean 提供了初始化方法的方式,它只包括 afterPropertiesSet()方法。
在 spring 初始化 bean 的時候,如果bean實現(xiàn)了 InitializingBean 接口,在對象的所有屬性被初始化后之后才會調(diào)用 afterPropertiesSet() 方法
@Component
public class InitialingzingBeanTest implements InitializingBean {
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("InitializingBean..");
}
}當(dāng)然,可以看出spring初始化bean肯定會在 ApplicationRunner 和 CommandLineRunner 接口調(diào)用之前。
當(dāng)然有一點要注意的是,盡管使用 InitialingBean 接口可以實現(xiàn)初始化動作,但是官方并不建議使用 InitializingBean 接口,因為它將你的代碼耦合在Spring代碼中,官方的建議是在 bean 的配置文件指定 init-method 方法,或者在 @Bean 中設(shè)置 init-method 屬性
init-method和@PostConstruct
前面就說過官方文檔上不建議使用 InitializingBean 接口,但是我們可以在 <bean> 元素的 init-method 屬性指定 bean 初始化之后的操作方法,或者在指定方法上加上 @PostConstruct 注解來制定該方法在初始化之后調(diào)用
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@PostConstruct
public void init() {
System.out.println("init...");
}
}更多關(guān)于Spring Bean的生命周期的內(nèi)容,請參閱Spring相關(guān)書籍或博客Spring Bean的生命周期
0x04:Spring的事件機(jī)制
Spring 的事件機(jī)制實際上是設(shè)計模式中觀察者模式的典型應(yīng)用。
觀察者模式定義了一個一對多的依賴關(guān)系,讓一個或多個觀察者對象監(jiān)聽一個主題對象。這樣一來,當(dāng)被觀察者狀態(tài)改變時,需要通知相應(yīng)的觀察者,使這些觀察者能夠自動更新
基礎(chǔ)概念
Spring的事件驅(qū)動模型由三部分組成
事件: ApplicationEvent,繼承自JDK的EventObject,所有事件都要繼承它,也就是被觀察者
事件發(fā)布者: ApplicationEventPublisher及ApplicationEventMulticaster接口,使用這個接口,就可以發(fā)布事件了
事件監(jiān)聽者: ApplicationListener,繼承JDK的EventListener,所有監(jiān)聽者都繼承它,也就是我們所說的觀察者,當(dāng)然我們也可以使用注解 @EventListener,效果是一樣的
事件
在Spring框架中,默認(rèn)對ApplicationEvent事件提供了如下支持:
ContextStartedEvent:ApplicationContext啟動后觸發(fā)的事件
ContextStoppedEvent:ApplicationContext停止后觸發(fā)的事件
ContextRefreshedEvent:ApplicationContext初始化或刷新完成后觸發(fā)的事件;(容器初始化完成后調(diào)用,所以我們可以利用這個事件做一些初始化操作)
ContextClosedEvent:ApplicationContext關(guān)閉后觸發(fā)的事件;(如web容器關(guān)閉時自動會觸發(fā)spring容器的關(guān)閉,如果是普通java應(yīng)用,需要調(diào)用ctx.registerShutdownHook();注冊虛擬機(jī)關(guān)閉時的鉤子才行)
構(gòu)造一個類繼承ApplicationEvent
public class TestEvent extends ApplicationEvent {
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;
}
}創(chuàng)建事件監(jiān)聽者
有兩種方法可以創(chuàng)建監(jiān)聽者,一種是直接實現(xiàn)ApplicationListener的接口,一種是使用注解 @EventListener,注解是添加在監(jiān)聽方法上的,下面的例子是直接實現(xiàn)的接口
@Component
public class ApplicationListenerTest implements ApplicationListener<TestEvent> {
@Override
public void onApplicationEvent(TestEvent testEvent){
testEvent.getMessage();
}
}事件發(fā)布
對于事件發(fā)布,代表者是ApplicationEventPublisher和ApplicationEventMulticaster,
ApplicationContext接口繼承了ApplicationEventPublisher,并在AbstractApplicationContext實現(xiàn)了具體代碼,實際執(zhí)行是委托給ApplicationEventMulticaster(可以認(rèn)為是多播)
下面是一個事件發(fā)布者的測試實例:
@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事件進(jìn)行初始化操作
前面做了這么多鋪墊,下面進(jìn)入今天的主題,利用Spring的事件機(jī)制進(jìn)行初始化一些操作,實際上就是前面提到了,利用ContextRefreshedEvent事件進(jìn)行初始化,該事件是ApplicationContext初始化完成后調(diào)用的事件,所以我們可以利用這個事件,對應(yīng)實現(xiàn)一個監(jiān)聽器,在其onApplicationEvent()方法里初始化操作
@Component
public class ApplicationListenerTest implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
System.out.println("我被調(diào)用了..");
}
}注意: 在傳統(tǒng)的基于XML配置的Spring項目中會存在二次調(diào)用的問題,即調(diào)用兩次該方法,原因是在傳統(tǒng)的Spring MVC項目中,系統(tǒng)存在兩個容器,一個root容器,一個project-servlet.xml對應(yīng)的子容器,在初始化這兩個容器的時候都會調(diào)用該方法一次,所以有二次調(diào)用的問題,而對于基于Springboot的項目不存在這個問題
小結(jié)
以上簡要總結(jié)了在springboot啟動時進(jìn)行初始化操作的幾個方案,這幾種方式都可以滿足我們的需求,針對具體場景使用對應(yīng)的方案。但是,CommandLineRunner或者ApplicationRunner不是Spring框架原有的東西,它倆屬于SpringBoot應(yīng)用特定的回調(diào)擴(kuò)展接口,所以很容易進(jìn)行擴(kuò)展,在一些微服務(wù)應(yīng)用中使用也較廣泛。
source: //pjmike.github.io/2018/08/16/springboot系列文章之啟動時初始化數(shù)據(jù)/

喜歡,在看
