六種方式,教你在SpringBoot初始化時(shí)搞點(diǎn)事情!
前言
在實(shí)際工作中總是需要在項(xiàng)目啟動(dòng)時(shí)做一些初始化的操作,比如初始化線程池、提前加載好加密證書.......
那么經(jīng)典問(wèn)題來(lái)了,這也是面試官經(jīng)常會(huì)問(wèn)到的一個(gè)問(wèn)題:有哪些手段在Spring Boot 項(xiàng)目啟動(dòng)的時(shí)候做一些事情?
方法有很多種,下面介紹幾種常見(jiàn)的方法。
1、監(jiān)聽(tīng)容器刷新完成擴(kuò)展點(diǎn)ApplicationListener<ContextRefreshedEvent>
ApplicationContext事件機(jī)制是觀察者設(shè)計(jì)模式實(shí)現(xiàn)的,通過(guò)ApplicationEvent和ApplicationListener這兩個(gè)接口實(shí)現(xiàn)ApplicationContext的事件機(jī)制。
Spring中一些內(nèi)置的事件如下:
ContextRefreshedEvent:ApplicationContext 被初始化或刷新時(shí),該事件被發(fā)布。這也可以在 ConfigurableApplicationContext接口中使用 refresh() 方法來(lái)發(fā)生。此處的初始化是指:所有的Bean被成功裝載,后處理Bean被檢測(cè)并激活,所有Singleton Bean 被預(yù)實(shí)例化,ApplicationContext容器已就緒可用。ContextStartedEvent:當(dāng)使用 ConfigurableApplicationContext (ApplicationContext子接口)接口中的 start() 方法啟動(dòng) ApplicationContext 時(shí),該事件被發(fā)布。你可以調(diào)查你的數(shù)據(jù)庫(kù),或者你可以在接受到這個(gè)事件后重啟任何停止的應(yīng)用程序。ContextStoppedEvent:當(dāng)使用 ConfigurableApplicationContext 接口中的 stop() 停止 ApplicationContext 時(shí),發(fā)布這個(gè)事件。你可以在接受到這個(gè)事件后做必要的清理的工作。ContextClosedEvent:當(dāng)使用 ConfigurableApplicationContext 接口中的 close() 方法關(guān)閉 ApplicationContext 時(shí),該事件被發(fā)布。一個(gè)已關(guān)閉的上下文到達(dá)生命周期末端;它不能被刷新或重啟。RequestHandledEvent:這是一個(gè) web-specific 事件,告訴所有 bean HTTP 請(qǐng)求已經(jīng)被服務(wù)。只能應(yīng)用于使用DispatcherServlet的Web應(yīng)用。在使用Spring作為前端的MVC控制器時(shí),當(dāng)Spring處理用戶請(qǐng)求結(jié)束后,系統(tǒng)會(huì)自動(dòng)觸發(fā)該事件。
好了,了解上面這些內(nèi)置事件后,我們可以監(jiān)聽(tīng)ContextRefreshedEvent在Spring Boot 啟動(dòng)時(shí)完成一些操作,代碼如下:
@Component
public class TestApplicationListener implements ApplicationListener<ContextRefreshedEvent>{
@Override
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
System.out.println(contextRefreshedEvent);
System.out.println("TestApplicationListener............................");
}
}
高級(jí)玩法
可以自定事件完成一些特定的需求,比如:郵件發(fā)送成功之后,做一些業(yè)務(wù)處理。
自定義EmailEvent,代碼如下:
public class EmailEvent extends ApplicationEvent{
private String address;
private String text;
public EmailEvent(Object source, String address, String text){
super(source);
this.address = address;
this.text = text;
}
public EmailEvent(Object source) {
super(source);
}
//......address和text的setter、getter
}
自定義監(jiān)聽(tīng)器,代碼如下:
public class EmailNotifier implements ApplicationListener{
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof EmailEvent) {
EmailEvent emailEvent = (EmailEvent)event;
System.out.println("郵件地址:" + emailEvent.getAddress());
System.our.println("郵件內(nèi)容:" + emailEvent.getText());
} else {
System.our.println("容器本身事件:" + event);
}
}
}
發(fā)送郵件后,觸發(fā)事件,代碼如下:
public class SpringTest {
public static void main(String args[]){
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
//創(chuàng)建一個(gè)ApplicationEvent對(duì)象
EmailEvent event = new EmailEvent("hello","[email protected]","This is a test");
//主動(dòng)觸發(fā)該事件
context.publishEvent(event);
}
}
2、SpringBoot的CommandLineRunner接口
當(dāng)容器初始化完成之后會(huì)調(diào)用CommandLineRunner中的run()方法,同樣能夠達(dá)到容器啟動(dòng)之后完成一些事情。這種方式和ApplicationListener相比更加靈活,如下:
不同的 CommandLineRunner實(shí)現(xiàn)可以通過(guò)@Order()指定執(zhí)行順序可以接收從控制臺(tái)輸入的參數(shù)。
下面自定義一個(gè)實(shí)現(xiàn)類,代碼如下:
@Component
@Slf4j
public class CustomCommandLineRunner implements CommandLineRunner {
/**
* @param args 接收控制臺(tái)傳入的參數(shù)
*/
@Override
public void run(String... args) throws Exception {
log.debug("從控制臺(tái)接收參數(shù)>>>>"+ Arrays.asList(args));
}
}
運(yùn)行這個(gè)jar,命令如下:
java -jar demo.jar aaa bbb ccc
以上命令中傳入了三個(gè)參數(shù),分別是aaa、bbb、ccc,這三個(gè)參數(shù)將會(huì)被run()方法接收到。如下圖:

源碼分析
讀過(guò)我的文章的鐵粉都應(yīng)該知道CommandLineRunner是如何執(zhí)行的,原文:頭禿系列,二十三張圖帶你從源碼分析Spring Boot 啟動(dòng)流程~
Spring Boot 加載上下文的入口在org.springframework.context.ConfigurableApplicationContext()這個(gè)方法中,如下圖:

調(diào)用CommandLineRunner在callRunners(context, applicationArguments);這個(gè)方法中執(zhí)行,源碼如下圖:

3、SpringBoot的ApplicationRunner接口
ApplicationRunner和CommandLineRunner都是Spring Boot 提供的,相對(duì)于CommandLineRunner來(lái)說(shuō)對(duì)于控制臺(tái)傳入的參數(shù)封裝更好一些,可以通過(guò)鍵值對(duì)來(lái)獲取指定的參數(shù),比如--version=2.1.0。
此時(shí)運(yùn)行這個(gè)jar命令如下:
java -jar demo.jar --version=2.1.0 aaa bbb ccc
以上命令傳入了四個(gè)參數(shù),一個(gè)鍵值對(duì)version=2.1.0,另外三個(gè)是分別是aaa、bbb、ccc。
同樣可以通過(guò)@Order()指定優(yōu)先級(jí),如下代碼:
@Component
@Slf4j
public class CustomApplicationRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
log.debug("控制臺(tái)接收的參數(shù):{},{},{}",args.getOptionNames(),args.getNonOptionArgs(),args.getSourceArgs());
}
}
通過(guò)以上命令運(yùn)行,結(jié)果如下圖:

源碼分析
和CommandLineRunner一樣,同樣在callRunners()這個(gè)方法中執(zhí)行,源碼如下圖:

4、@PostConstruct注解
前三種針對(duì)的是容器的初始化完成之后做的一些事情,@PostConstruct這個(gè)注解是針對(duì)Bean的初始化完成之后做一些事情,比如注冊(cè)一些監(jiān)聽(tīng)器...
@PostConstruct注解一般放在Bean的方法上,一旦Bean初始化完成之后,將會(huì)調(diào)用這個(gè)方法,代碼如下:
@Component
@Slf4j
public class SimpleExampleBean {
@PostConstruct
public void init(){
log.debug("Bean初始化完成,調(diào)用...........");
}
}
5、@Bean注解中指定初始化方法
這種方式和@PostConstruct比較類似,同樣是指定一個(gè)方法在Bean初始化完成之后調(diào)用。
新建一個(gè)Bean,代碼如下:
@Slf4j
public class SimpleExampleBean {
public void init(){
log.debug("Bean初始化完成,調(diào)用...........");
}
}
在配置類中通過(guò)@Bean實(shí)例化這個(gè)Bean,不過(guò)@Bean中的initMethod這個(gè)屬性需要指定初始化之后需要執(zhí)行的方法,如下:
@Bean(initMethod = "init")
public SimpleExampleBean simpleExampleBean(){
return new SimpleExampleBean();
}
6、 InitializingBean接口
InitializingBean的用法基本上與@PostConstruct一致,只不過(guò)相應(yīng)的Bean需要實(shí)現(xiàn)afterPropertiesSet方法,代碼如下:
@Slf4j
@Component
public class SimpleExampleBean implements InitializingBean {
@Override
public void afterPropertiesSet() {
log.debug("Bean初始化完成,調(diào)用...........");
}
}
— 【 THE END 】— 本公眾號(hào)全部博文已整理成一個(gè)目錄,請(qǐng)?jiān)诠娞?hào)里回復(fù)「m」獲??! 最近面試BAT,整理一份面試資料《Java面試BATJ通關(guān)手冊(cè)》,覆蓋了Java核心技術(shù)、JVM、Java并發(fā)、SSM、微服務(wù)、數(shù)據(jù)庫(kù)、數(shù)據(jù)結(jié)構(gòu)等等。
獲取方式:點(diǎn)“在看”,關(guān)注公眾號(hào)并回復(fù) PDF 領(lǐng)取,更多內(nèi)容陸續(xù)奉上。
文章有幫助的話,在看,轉(zhuǎn)發(fā)吧。
謝謝支持喲 (*^__^*)
