一、前言
上篇文章我們深入分析了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,那么有兩種方式:
-
通過SpringApplication.addListeners(…)或者SpringApplication.setListener(…)方法添加一個或者多個自定義的ApplicationListener。
-
借助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;
}
關於這貨,我們需要關注的點有兩個:
- 所有CommandLineRunner的執行時間點是在SpringBoot應用的Application完全初始化工作之后(這里我們可以認為是SpringBoot應用啟動類main方法執行完成之前的最后一步)。
- 當前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)"也必將統領江山!