六種方式,教你在SpringBoot初始化時搞點事情!

bucaichenmou發表於2021-08-23

前言

在實際工作中總是需要在專案啟動時做一些初始化的操作,比如初始化執行緒池、提前載入好加密證照.......

那麼經典問題來了,這也是面試官經常會問到的一個問題:有哪些手段在Spring Boot 專案啟動的時候做一些事情?

方法有很多種,下面介紹幾種常見的方法。

1、監聽容器重新整理完成擴充套件點ApplicationListener<ContextRefreshedEvent>

ApplicationContext事件機制是觀察者設計模式實現的,通過ApplicationEvent和ApplicationListener這兩個介面實現ApplicationContext的事件機制。

Spring中一些內建的事件如下:

  1. ContextRefreshedEvent:ApplicationContext 被初始化或重新整理時,該事件被髮布。這也可以在 ConfigurableApplicationContext介面中使用 refresh() 方法來發生。此處的初始化是指:所有的Bean被成功裝載,後處理Bean被檢測並啟用,所有Singleton Bean 被預例項化,ApplicationContext容器已就緒可用。
  2. ContextStartedEvent:當使用 ConfigurableApplicationContext (ApplicationContext子介面)介面中的 start() 方法啟動 ApplicationContext 時,該事件被髮布。你可以調查你的資料庫,或者你可以在接受到這個事件後重啟任何停止的應用程式。
  3. ContextStoppedEvent:當使用 ConfigurableApplicationContext 介面中的 stop() 停止 ApplicationContext 時,釋出這個事件。你可以在接受到這個事件後做必要的清理的工作。
  4. ContextClosedEvent:當使用 ConfigurableApplicationContext 介面中的 close() 方法關閉 ApplicationContext 時,該事件被髮布。一個已關閉的上下文到達生命週期末端;它不能被重新整理或重啟。
  5. 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............................");
    }
}

高階玩法

可以自定事件完成一些特定的需求,比如:郵件傳送成功之後,做一些業務處理。

  1. 自定義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
}
  1. 自定義監聽器,程式碼如下:
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);
     }
   }
}
  1. 傳送郵件後,觸發事件,程式碼如下:
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、SpringBootCommandLineRunner介面

當容器初始化完成之後會呼叫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

以上命令中傳入了三個引數,分別是aaabbbccc,這三個引數將會被run()方法接收到。如下圖:

原始碼分析

讀過我的文章的鐵粉都應該知道CommandLineRunner是如何執行的

Spring Boot 載入上下文的入口在org.springframework.context.ConfigurableApplicationContext()這個方法中,如下圖:

呼叫CommandLineRunner在callRunners(context, applicationArguments);這個方法中執行,原始碼如下圖:

3、SpringBootApplicationRunner介面

ApplicationRunnerCommandLineRunner都是Spring Boot 提供的,相對於CommandLineRunner來說對於控制檯傳入的引數封裝更好一些,可以通過鍵值對來獲取指定的引數,比如--version=2.1.0

此時執行這個jar命令如下:

java -jar demo.jar --version=2.1.0 aaa bbb ccc

以上命令傳入了四個引數,一個鍵值對version=2.1.0,另外三個是分別是aaabbbccc

同樣可以通過@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初始化完成,呼叫...........");
    }
}

總結

實現方案有很多,作者只是總結了常用的六種,學會的點個贊。

相關文章