前言
在實際工作中總是需要在專案啟動時做一些初始化的操作,比如初始化執行緒池、提前載入好加密證照.......
那麼經典問題來了,這也是面試官經常會問到的一個問題:有哪些手段在Spring Boot 專案啟動的時候做一些事情?
方法有很多種,下面介紹幾種常見的方法。
1、監聽容器重新整理完成擴充套件點ApplicationListener<ContextRefreshedEvent>
ApplicationContext事件機制是觀察者設計模式實現的,通過ApplicationEvent和ApplicationListener這兩個介面實現ApplicationContext的事件機制。
Spring中一些內建的事件如下:
ContextRefreshedEvent
:ApplicationContext 被初始化或重新整理時,該事件被髮布。這也可以在 ConfigurableApplicationContext介面中使用 refresh() 方法來發生。此處的初始化是指:所有的Bean被成功裝載,後處理Bean被檢測並啟用,所有Singleton Bean 被預例項化,ApplicationContext容器已就緒可用。ContextStartedEvent
:當使用 ConfigurableApplicationContext (ApplicationContext子介面)介面中的 start() 方法啟動 ApplicationContext 時,該事件被髮布。你可以調查你的資料庫,或者你可以在接受到這個事件後重啟任何停止的應用程式。ContextStoppedEvent
:當使用 ConfigurableApplicationContext 介面中的 stop() 停止 ApplicationContext 時,釋出這個事件。你可以在接受到這個事件後做必要的清理的工作。ContextClosedEvent
:當使用 ConfigurableApplicationContext 介面中的 close() 方法關閉 ApplicationContext 時,該事件被髮布。一個已關閉的上下文到達生命週期末端;它不能被重新整理或重啟。RequestHandledEvent
:這是一個 web-specific 事件,告訴所有 bean HTTP 請求已經被服務。只能應用於使用DispatcherServlet的Web應用。在使用Spring作為前端的MVC控制器時,當Spring處理使用者請求結束後,系統會自動觸發該事件。
好了,瞭解上面這些內建事件後,我們可以監聽ContextRefreshedEvent
在Spring Boot 啟動時完成一些操作,程式碼如下:
@Component
public class TestApplicationListener implements ApplicationListener<ContextRefreshedEvent>{
@Override
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
System.out.println(contextRefreshedEvent);
System.out.println("TestApplicationListener............................");
}
}
高階玩法
可以自定事件完成一些特定的需求,比如:郵件傳送成功之後,做一些業務處理。
- 自定義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
}
- 自定義監聽器,程式碼如下:
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("郵件內容:" + emailEvent.getText());
} else {
System.our.println("容器本身事件:" + event);
}
}
}
- 傳送郵件後,觸發事件,程式碼如下:
public class SpringTest {
public static void main(String args[]){
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
//建立一個ApplicationEvent物件
EmailEvent event = new EmailEvent("hello","abc@163.com","This is a test");
//主動觸發該事件
context.publishEvent(event);
}
}
2、SpringBoot
的CommandLineRunner
介面
當容器初始化完成之後會呼叫CommandLineRunner
中的run()
方法,同樣能夠達到容器啟動之後完成一些事情。這種方式和ApplicationListener相比更加靈活,如下:
- 不同的
CommandLineRunner
實現可以通過@Order()
指定執行順序 - 可以接收從控制檯輸入的引數。
下面自定義一個實現類,程式碼如下:
@Component
@Slf4j
public class CustomCommandLineRunner implements CommandLineRunner {
/**
* @param args 接收控制檯傳入的引數
*/
@Override
public void run(String... args) throws Exception {
log.debug("從控制檯接收引數>>>>"+ Arrays.asList(args));
}
}
執行這個jar,命令如下:
java -jar demo.jar aaa bbb ccc
以上命令中傳入了三個引數,分別是aaa
、bbb
、ccc
,這三個引數將會被run()
方法接收到。如下圖:
原始碼分析
讀過我的文章的鐵粉都應該知道CommandLineRunner
是如何執行的
Spring Boot 載入上下文的入口在org.springframework.context.ConfigurableApplicationContext()
這個方法中,如下圖:
呼叫CommandLineRunner在callRunners(context, applicationArguments);
這個方法中執行,原始碼如下圖:
3、SpringBoot
的ApplicationRunner
介面
ApplicationRunner
和CommandLineRunner
都是Spring Boot 提供的,相對於CommandLineRunner
來說對於控制檯傳入的引數封裝更好一些,可以通過鍵值對來獲取指定的引數,比如--version=2.1.0
。
此時執行這個jar命令如下:
java -jar demo.jar --version=2.1.0 aaa bbb ccc
以上命令傳入了四個引數,一個鍵值對version=2.1.0
,另外三個是分別是aaa
、bbb
、ccc
。
同樣可以通過@Order()
指定優先順序,如下程式碼:
@Component
@Slf4j
public class CustomApplicationRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
log.debug("控制檯接收的引數:{},{},{}",args.getOptionNames(),args.getNonOptionArgs(),args.getSourceArgs());
}
}
通過以上命令執行,結果如下圖:
原始碼分析
和CommandLineRunner
一樣,同樣在callRunners()
這個方法中執行,原始碼如下圖:
4、@PostConstruct
註解
前三種針對的是容器的初始化完成之後做的一些事情,@PostConstruct
這個註解是針對Bean
的初始化完成之後做一些事情,比如註冊一些監聽器...
@PostConstruct
註解一般放在Bean的方法上,一旦Bean初始化完成之後,將會呼叫這個方法,程式碼如下:
@Component
@Slf4j
public class SimpleExampleBean {
@PostConstruct
public void init(){
log.debug("Bean初始化完成,呼叫...........");
}
}
5、@Bean註解中指定初始化方法
這種方式和@PostConstruct
比較類似,同樣是指定一個方法在Bean初始化完成之後呼叫。
新建一個Bean,程式碼如下:
@Slf4j
public class SimpleExampleBean {
public void init(){
log.debug("Bean初始化完成,呼叫...........");
}
}
在配置類中通過@Bean
例項化這個Bean,不過@Bean
中的initMethod
這個屬性需要指定初始化之後需要執行的方法,如下:
@Bean(initMethod = "init")
public SimpleExampleBean simpleExampleBean(){
return new SimpleExampleBean();
}
6、 InitializingBean
介面
InitializingBean
的用法基本上與@PostConstruct
一致,只不過相應的Bean
需要實現afterPropertiesSet
方法,程式碼如下:
@Slf4j
@Component
public class SimpleExampleBean implements InitializingBean {
@Override
public void afterPropertiesSet() {
log.debug("Bean初始化完成,呼叫...........");
}
}
總結
實現方案有很多,作者只是總結了常用的六種,學會的點個贊。