springboot資源初始化的幾種方式

摘要

在實際項目中,咱們須要在springboot服務啓動後作一些初始化工做,例如線程池初始化、文件資源加載、常駐後臺任務啓動(好比kafka consumer)等。本文介紹3類初始化資源的方法:html

  • Spring Bean初始化的InitializingBean,init-method和PostConstruct
  • ApplicationRunnerCommandLineRunner接口
  • Spring的事件機制

方法1:spring bean 初始化

Spring 容器中的 Bean 是有生命週期的,Spring 容許在 Bean 在初始化完成後以及 Bean 銷燬前執行特定的操做,經常使用的設定方式有如下三種:java

  • 經過實現 InitializingBean接口來定製初始化以後的操做方法;
  • 經過 <bean> 元素的 init-method屬性指定初始化以後調用的操做方法;
  • 在指定方法上加上@PostConstruct 註解來指定該在初始化以後調用的方法

執行的前後順序:構造方法 --> @PostConstruct註解的方法 --> afterPropertiesSet方法(InitializingBean接口) --> init-method指定的方法。詳情參考Spring Bean 初始化之InitializingBean, init-method 和 PostConstructgit

方法2:ApplicationRunner與CommandLineRunner接口

CommandLineRunner/ApplicationRunner 接口的 Component 會在全部 Spring Beans都初始化以後,SpringApplication.run()以前執行,很是適合在應用程序啓動之初進行一些數據初始化的工做。CommandLineRunnerApplicationRunner這兩個接口工做方式相同,都只提供單一的run方法,惟一的區別是run方法的入參類型不一樣,CommandLineRunner的參數是最原始的參數,沒有進行任何處理,ApplicationRunner的參數是ApplicationArguments,是對原始參數的進一步封裝。github

runner定義

如下定義了兩個runner,其中Order註解指定了執行順序,數字越小越先執行web

@Order(1)
@Component
@Slf4j
public class MyCommandLineRunner implements CommandLineRunner {
    @Override
    public void run(String... args) {
        log.info("MyCommandLineRunner run...");
    }
}
-------------------------------------------------------------------
@Order(2)
@Component
@Slf4j
public class MyApplicationRunner implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) throws Exception {
        log.info("MyApplicationRunner run...");
    }
}

源碼跟蹤

跟蹤源碼,看下CommandLineRunner/ApplicationRunner是如何被調用的,Springboot在啓動的時候,都會構造一個SpringApplication實例,執行run方法,一路點進去後不難發現調用入口是SpringApplication.run方法中的callRunners(context, applicationArguments)spring

public ConfigurableApplicationContext run(String... args) {
        ......
            listeners.started(context);
            callRunners(context, applicationArguments);
        }
        catch (Throwable ex) {
            handleRunFailure(context, listeners, exceptionReporters, ex);
            throw new IllegalStateException(ex);
        }
        listeners.running(context);
        return context;
    }

總結

  • 全部CommandLineRunner/ApplicationRunner的執行時間點是在SpringBoot應用的ApplicationContext徹底初始化開始工做以後,callRunners()能夠看出是run方法內部最後一個調用的方法(能夠認爲是main方法執行完成以前最後一步)
  • 只要存在於當前SpringBoot應用的ApplicationContext中的任何CommandLineRunner/ApplicationRunner,都會被加載執行(無論你是手動註冊仍是自動掃描去Ioc容器)

方法3:spring事件機制

Spring的事件機制其實是設計模式中觀察者模式的典型應用。觀察者模式定義了一個一對多的依賴關係,讓一個或多個觀察者對象監聽一個主題對象。這樣一來,當被觀察者狀態改變時,須要通知相應的觀察者,使這些觀察者可以自動更新。segmentfault

Spring的事件驅動模型由三部分組成設計模式

  • 事件: ApplicationEvent,繼承自JDK的EventObject,全部事件都要繼承它,也就是被觀察者
  • 事件發佈者: ApplicationEventPublisherApplicationEventMulticaster接口,使用這個接口,就能夠發佈事件了
  • 事件監聽者::ApplicationListener,繼承JDK的EventListener,全部監聽者都繼承它,也就是咱們所說的觀察者,固然咱們也可使用註解 @EventListener,效果是同樣的

built-in事件

在Spring框架中,內置了4種事件:springboot

  • ContextStartedEvent:ApplicationContext啓動後觸發的事件
  • ContextStoppedEvent:ApplicationContext中止後觸發的事件
  • ContextRefreshedEvent:ApplicationContext初始化或刷新完成後觸發的事件;(容器初始化完成後調用,因此咱們能夠利用這個事件作一些初始化操做)
  • ContextClosedEvent:ApplicationContext關閉後觸發的事件;(如web容器關閉時自動會觸發spring容器的關閉,若是是普通java應用,須要調用ctx.registerShutdownHook();註冊虛擬機關閉時的鉤子才行)

例子

ContextRefreshedEvent爲例,建立一個listener很是簡單app

@Component
@Slf4j
public class MyContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        log.info("ContextRefreshedEvent listen... ");
    }
}

MyContextRefreshListener監聽了內置事件ContextRefreshedEvent,即容器初始化完成後調用,MyContextRefreshListener.onApplicationEvent會被調用,利用此特性能夠作一些初始化工做

注意: 在傳統的基於XML配置的Spring項目中會存在二次調用的問題,即調用兩次該方法,緣由是在傳統的Spring MVC項目中,系統存在兩個容器,一個root容器,一個project-servlet.xml對應的子容器,在初始化這兩個容器的時候都會調用該方法一次,因此有二次調用的問題,而對於基於Springboot的項目不存在這個問題

三種方式的執行順序

方法1(spring bean初始化) --> spring事件ContextRefreshedEvent--> CommandLineRunner/ApplicationRunner,示例代碼下載請戳代碼下載地址

參考文檔

相關文章
相關標籤/搜索