SpringBoot各類擴充套件點詳解

阿豪聊乾貨發表於2018-06-08

一、前言

  上篇文章我們深入分析了SpringBoot的一站式啟動流程。然後我們知道SpringBoot的主要功能都是依靠它內部很多的擴充套件點來完成的,那毋容置疑,這些擴充套件點是我們應該深入瞭解的,那麼本次且聽我一一道來SpringBoot的各類擴充套件點。

二、SpringBoot各類擴充套件點詳解

  下面我們就一一為大家來解析這些必須的擴充套件點:

1.SpringApplicationRunListener

​   從命名我們就可以知道它是一個監聽者,那縱觀整個啟動流程我們會發現,它其實是用來在整個啟動流程中接收不同執行點事件通知的監聽者。原始碼如下:

public interface SpringApplicationRunListener {
void starting();
void environmentPrepared(ConfigurableEnvironment environment);
void contextPrepared(ConfigurableApplicationContext context);
void contextLoaded(ConfigurableApplicationContext context);
void finished(ConfigurableApplicationContext context, Throwable exception);
}

​   對於開發者來說,基本沒有什麼常見的場景要求我們必須實現一個自定義的SpringApplicationRunListener,即使是SpringBoot中也只預設實現了一個org.springframework.boot.context.eventEventPublishingRunListener, 用來在SpringBoot的整個啟動流程中的不同時間點發布不同型別的應用事件(SpringApplicationEvent)。那些對這些應用事件感興趣的ApplicationListener可以接受並處理(這也解釋了為什麼在SpringApplication例項化的時候載入了一批ApplicationListener,但在run方法執行的過程中並沒有被使用)。

​  如果我們真的在實際場景中自定義實現SpringApplicationRunListener,有一個點需要注意:任何一個SpringApplicationRunListener實現類的構造方法都需要有兩個構造引數,一個引數的型別就是我們的org.springframework.boot.SpringApplication,另外一個引數就是args引數列表的String[]:

package com.hafiz.springbootdemo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringApplicationRunListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;

/**
* @author hafiz.zhang
* @description:
* @date Created in 2018/6/7 20:07.
*/

public class DemoSpringApplicationRunListener implements SpringApplicationRunListener {
private final SpringApplication application;
private final String[] args;
public DemoSpringApplicationRunListener(SpringApplication sa, String[] args) {
this.application = sa;
this.args = args;
}

@Override
public void starting() {
System.out.println("自定義starting");
}

@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
System.out.println("自定義environmentPrepared");
}

@Override
public void contextPrepared(ConfigurableApplicationContext context) {
System.out.println("自定義contextPrepared");
}

@Override
public void contextLoaded(ConfigurableApplicationContext context) {
System.out.println("自定義contextLoaded");
}

@Override
public void finished(ConfigurableApplicationContext context, Throwable exception) {
System.out.println("自定義finished");
}
}

​   接著,我們還要滿足SpringFactoriesLoader的約定,在當前SpringBoot專案的classpath下新建META-INF目錄,並在該目錄下新建spring.fatories檔案,檔案內容如下:

org.springframework.boot.SpringApplicationRunListener=\
com.hafiz.springbootdemo.DemoSpringApplicationRunListener

2.ApplicationListener

​  ApplicationListener不是新東西,它屬於Spring框架對Java中實現的監聽者模式的一種框架實現,這裡需要注意的是:對於剛接觸SpringBoot,但是對於Spring框架本身又沒有過多地接觸的開發人員來說,可能會將這個名字與SpringApplicationRunListener弄混。

​如果我們有需要為SpringBoot應用新增我們自定義的ApplicationListener,那麼有兩種方式:

  1. 通過SpringApplication.addListeners(…)或者SpringApplication.setListener(…)方法新增一個或者多個自定義的ApplicationListener。

  2. 藉助SpringFactoriesLoader機制,在SpringBoot的專案自定義的META-INF/spring.factories檔案中新增配置(以下是SpringBoot預設的ApplicationListener配置):

    org.springframework.context.ApplicationListener=\
    org.springframework.boot.ClearCachesApplicationListener,\
    org.springframework.boot.builder.ParentContextCloserApplicationListener,\
    org.springframework.boot.context.FileEncodingApplicationListener,\
    org.springframework.boot.context.config.AnsiOutputApplicationListener,\
    org.springframework.boot.context.config.ConfigFileApplicationListener,\
    org.springframework.boot.context.config.DelegatingApplicationListener,\
    org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener,\
    org.springframework.boot.logging.ClasspathLoggingApplicationListener,\
    org.springframework.boot.logging.LoggingApplicationListener

3.ApplicationContextInitializer

​   這貨也是Spring框架原有的東西,這個類的主要作用就是在ConfigurableApplicationContext型別(或者子型別)的ApplicationContext做refresh之前,允許我們對ConfiurableApplicationContext的例項做進一步的設定和處理。

​   我們要實現一個自定義的ApplicationContextInitializer也很簡單,它只有一個方法需要我們的自定義類實現:

package com.hafiz.springbootdemo;

import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;

/**
* @author hafiz.zhang
* @description:
* @date Created in 2018/6/7 20:33.
*/

public class DemoApplicationContextInitializer implements ApplicationContextInitializer {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
System.out.println("自定義DemoApplicationContextInitializer的initialize方法");
}
}

然後也需要在專案自定義的META-INF/spring.factories檔案中註冊:

org.springframework.context.ApplicationContextInitializer=\
com.hafiz.springbootdemo.DemoApplicationContextInitializer

  不過我們一般情況下是不需要自定義一個ApplicationContextInitializer,即使SpringBoot框架預設也只有以下四個實現而已:

org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.context.embedded.ServerPortInfoApplicationContextInitializer

4.CommandLineRunner

​   CommandLineRunner並不是Spring框架原有的概念,它屬於SpringBoot應用特定的回撥擴充套件介面:

public interface CommandLineRunner {
/**
* Callback used to run the bean.
* @param args incoming main method arguments
* @throws Exception on error
*/

void run(String... args) throws Exception;

}

關於這貨,我們需要關注的點有兩個:

  1. 所有CommandLineRunner的執行時間點是在SpringBoot應用的Application完全初始化工作之後(這裡我們可以認為是SpringBoot應用啟動類main方法執行完成之前的最後一步)。
  2. 當前SpringBoot應用的ApplicationContext中的所有CommandLinerRunner都會被載入執行(無論是手動註冊還是被自動掃描註冊到IoC容器中)。

  跟其他幾個擴充套件點介面型別相似,我們建議CommandLineRunner的實現類使用@org.springframework.core.annotation.Order進行標註或者實現org.springframework.core.Ordered介面,便於對他們的執行順序進行排序調整,這是非常有必要的,因為我們不希望不合適的CommandLineRunner實現類阻塞了後面其他CommandLineRunner的執行。這個介面非常有用和重要,我們需要重點關注。

三、你不知道的自動配置奧祕

​   上篇文章我們知道了,@EnableAutoConfiguration藉助SpringFactoriesLoader可以將標註了@Configuration這個註解的JavaConfig類一併彙總並載入到最終的ApplicationContext,這麼說只是很簡單的解釋,其實基於@EnableAutoConfiguration的自動配置功能擁有非常強大的調控能力。比如我們可以通過配合基於條件的配置能力或定製化載入順序,對自動化配置進行更加細粒度的調整和控制。

1.基於條件的自動配置

​  這個基於條件的自動配置來源於Spring框架中的"基於條件的配置"特性。在Spring框架中,我們可以使用@Conditional這個註解配合@Configuration@Bean等註解來干預一個配置或bean定義是否能夠生效,它最終實現的效果或者語義類如下虛擬碼:

if (複合@Conditional規定的條件) {
載入當前配置(Enable Current Configuration)或者註冊當前bean定義;
}

  要實現基於條件的配置,我們需要通過@Conditional註解指定自己Condition實現類就可以了(可以應用於型別Type的註解或者方法Method的註解)

@Conditional({DemoCondition1.class, DemoCondition2.class})

  最重要的是,@Conditional註解可以作為一個Meta Annotaion用來標註其他註解實現類,從而構建各種複合註解,比如SpringBoot的autoconfigre模組就基於這一優良的革命傳統,實現了一批這樣的註解(在org.springframework.boot.autoconfigure.condition包下):

  • @ConditionOnClass
  • @ConditionOnBean
  • @CondtionOnMissingClass
  • @CondtionOnMissingBean
  • @CondtionOnProperty
  • ……

​  有了這些複合Annotation的配合,我們就可以結合@EnableAutoConfiguration實現基於條件的自動配置了。其實說白了,SpringBoot能夠如此的盛行,很重要的一部分就是它預設提供了一系列自動配置的依賴模組,而這些依賴模組都是基於以上的@Conditional複合註解實現的,這也就說明這些所有的依賴模組都是按需載入的,只有複合某些特定的條件,這些依賴模組才會生效,這也解釋了為什麼自動配置是“智慧”的。

2.定製化自動配置的順序

​  在實現自動配置的過程中,我們除了可以提供基於條件的配置之外,我們還能對當前要提供的配置或元件的載入順序進行個性化調整,以便讓這些配置或者元件之間的依賴分析和組裝能夠順利完成。

​  最經典的是我們可以通過使用@org.springframework.boot.autoconfigure.AutoConfigureBefore或者@org.springframework.boot.autoconfigure.AutoConfigureAfter讓當前配置或者元件在某個其他元件之前或者之後進行配置。例如,假如我們希望某些JMX操作相關的bean定義在MBeanServer配置完成以後在進行配置,那我們就可以提供如下配置:

@Configuration
@AutoConfigureAfter(JmxAutoConfiguration.class)
public class AfterMBeanServerReadyConfiguration {
@Autowired
MBeanServer mBeanServer;

// 通過@Bean新增其他必要的bean定義
}

四、總結

​  截至目前,我們已經完成了對SpringBoot的核心元件一一解析,總結來說,SpringBoot中大部分東西都是Spring框架中已經存在原有概念和實踐方式,SpringBoot只是在這基礎上對特定的場景進行定製、固化以及升級。然後正式這些固化升級讓我們感受到了SpringBoot開發的便捷以及高效。

​  然後,你會突然發現,SpringBoot原來是這麼的簡單,其中並無任何祕密。但是Spring團隊通過對Spring應用的固化和升級,讓SpringBoot可以完成開發只關注業務邏輯開發的神奇事件,整個開發過程更加高效以及簡單,但是對於SpringBoot的設計者來說,他們並沒有耗費巨大的精力。另一方面來說,開箱即用才是未來發展的趨勢,"約定大於配置(Convention Over Configuration)"也必將統領江山!

相關文章