微信公眾號:glmapper工作室
掘金專欄:glmapper
微 博:瘋狂的石頭_henu
歡迎關注,一起學習、一起分享
SpringBoot 作為目前非常流行的微服務框架,它使得構建獨立的 Spring 生產級應用變得非常簡單,因此受到很多網際網路企業的青睞。
推薦閱讀
- SpringBoot 系列-FatJar 啟動原理
- SpringBoot 系列-啟動過程分析
- SpringBoot 系列-事件機制詳解
- SpringBoot 系列-內嵌 Tomcat 的實現原理解析
- SpringBoot 系列-Kafka簡介&整合SpringBoot
背景
最近在寫 SOFATracer 整合 Spring Cloud Stream RocketMQ 的過程中,遇到了一些問題,比如:BeanPostProcessor 不生效,如何在 BeanPostProcessor 不生效的情況下去修改一個 Bean 等,這些問題其實都是和 Bean 的生命週期有關係的,當然也和容器啟動的過程有關係。SpringBoot 的啟動過程對於我來說其實不算陌生,也可以說是比較熟悉,但是之前沒有完整的梳理過這一款的東西,再實際的應用過程成難免再去踩一些坑。另外想到之前也寫過一篇 SpringBoot系列- FatJar 啟動原理,剛好承接上篇,繼續來探索 SpringBoot 中的一些知識點。
注:本篇基於 SpringBoot 2.1.0.RELEASE 版本,SpringBoot 各個版本之間可能存在差異,不過大體流程基本差不多,所以各位看官在實際的工作過程中也
啟動入口
在這篇SpringBoot系列- FatJar 啟動原理 文章中介紹得到,JarLaunch 最後是構建了一個 MainMethodRunner 例項物件,然後通過反射的方式呼叫了 BootStrap 類中的 main 方法,這裡的 ’BootStrap 類中的 main 方法‘ 實際上就是 SpringBoot 的業務入口,也就是常見的下面的程式碼片段:
@SpringBootApplication
public class GlmapperApplication {
public static void main(String[] args) {
SpringApplication.run(GlmapperApplication.class, args);
}
}
複製程式碼
從程式碼可以非常直觀的瞭解到,啟動是通過呼叫 SpringApplication 的靜態方法 run;這個 run 方法內部其實是會構造一個 SpringApplication 的例項,然後再呼叫這裡例項的 run 方法來啟動 SpringBoot的。
/**
* Static helper that can be used to run a {@link SpringApplication} from the
* specified sources using default settings and user supplied arguments.
* @param primarySources the primary sources to load
* @param args the application arguments (usually passed from a Java main method)
* @return the running {@link ApplicationContext}
*/
public static ConfigurableApplicationContext run(Class<?>[] primarySources,
String[] args) {
return new SpringApplication(primarySources).run(args);
}
複製程式碼
因此,如果要分析 SpringBoot 的啟動過程,我們需要熟悉 SpringApplication 的構造過程以及 SpringApplication 的 run 方法執行過程即可。
SpringApplication 例項的構建
篇幅原因,我們只分析核心的構建流程。
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
// 資源載入器,預設是 null
this.resourceLoader = resourceLoader;
// 啟動類 bean
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 是否是 web 應用
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 設定了 ApplicationContextInitializer
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
// 設定 ApplicationListener
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 啟動類
this.mainApplicationClass = deduceMainApplicationClass();
}
複製程式碼
上面程式碼段中,需要關注兩個點:
- 1、初始化 ApplicationContextInitializer;
- 2、初始化 ApplicationListener
要注意的是這裡的例項化,並非是通過註解和掃包完成,而是通過一種不依賴 Spring 上下文的載入方法;這種做法是為了能夠使得在 Spring 完成啟動前做各種配置。Spring 的解決方法是以介面的全限定名作為 key,實現類的全限定名作為 value 記錄在專案的 META-INF/spring.factories 檔案中,然後通過SpringFactoriesLoader 工具類提供靜態方法進行類載入並快取下來,spring.factories 是Spring Boot 的核心配置檔案。SpringFactoriesLoader 可以理解為 Spring 自己提供的一種 spi 擴充套件實現。SpringBoot 中提供的預設的 spring.factories 配置如下:
# PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=\
// ..省略
# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
// ..省略
# Error Reporters
org.springframework.boot.SpringBootExceptionReporter=\
// ..省略
# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\/
// ..省略
# Application Listeners
org.springframework.context.ApplicationListener=\
// ..省略
# Environment Post Processors
org.springframework.boot.env.EnvironmentPostProcessor=\
// ..省略
# Failure Analyzers
org.springframework.boot.diagnostics.FailureAnalyzer=\
// ..省略
# FailureAnalysisReporters
org.springframework.boot.diagnostics.FailureAnalysisReporter=\
// ..省略
複製程式碼
關於 SpringFactoriesLoader 如何載入這些資源這裡就不過多分析,有興趣的讀者可以自行檢視相關原始碼。org.springframework.core.io.support.SpringFactoriesLoader#loadSpringFactories
run 方法主流程
這裡先直觀的看下程式碼,然後再逐個分析:
public ConfigurableApplicationContext run(String... args) {
// 開啟容器啟動計時
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
// SpringBootExceptionReporter 列表,SpringBoot 允許自定義 Reporter
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
// 設定java.awt.headless屬性為true還是false
// 可詳見解釋:https://blog.csdn.net/michaelgo/article/details/81634017
configureHeadlessProperty();
// 獲取所有 SpringApplicationRunListener ,也是通過 SpringFactoriesLoader 來獲取的
SpringApplicationRunListeners listeners = getRunListeners(args);
// 釋出 starting 事件,在首次啟動 run方法時立即呼叫,可用於非常早的初始化,注意此時容器上下文還沒有重新整理
listeners.starting();
try {
// 構建 ApplicationArguments 物件
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
// 準備上下文重新整理需要的環境屬性 -- 詳見 prepareEnvironment 過程分析
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
// spring.beaninfo.ignore,如果為空設定為true
configureIgnoreBeanInfo(environment);
// 列印 SpringBoot 啟動 Banner
Banner printedBanner = printBanner(environment);
// 建立上下文,這裡會根據 webApplicationType 型別來建立不同的 ApplicationContext
context = createApplicationContext();
// 載入獲取 exceptionReporters
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// 上下文重新整理之前的準備工作 -- 詳見 prepareContext 過程分析
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
// 重新整理上下文 -- 詳見 refreshContext 過程分析
refreshContext(context);
// 重新整理之後回撥,SpringBoot 中這個方法是空實現,可以自行擴充套件
afterRefresh(context, applicationArguments);
// 停止計時
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
// 釋出 started 事件
listeners.started(context);
// ApplicationRunner 和 CommandLineRunner 呼叫
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
// 異常處理
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
// 釋出 running 事件
listeners.running(context);
}
catch (Throwable ex) {
// 異常處理
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
複製程式碼
上面對程式碼基本都做了一些簡單的註釋,有幾個需要關注的點:
- 1、prepareEnvironment 的處理過程
- 2、prepareContext 的處理過程
- 3、refreshContext 的處理過程
- 4、listeners 執行時機及順序
- 5、異常處理邏輯
關於 Listeners 執行時機及順序在之前的文章中有做過非常詳細的分析,詳見:SpringBoot 系列-事件機制詳解。下面就對其他的 4 個點做下詳細的分析。
分析啟動過程,本質上是對其整個容器生命週期有個瞭解,包括 listeners 執行各個事件的時機、PostProcessor 執行的時機,Enviroment Ready 的時機等等。掌握這些擴充套件和時機,可以在實際的業務開發中來做很多事情。
prepareEnvironment 的處理過程
prepareEnvironment 過程相對來說是比較早的,這裡主要就是為上下文重新整理提供 Environment。
private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
// 配置 PropertySources 和 Profiles
// 1、將引數和一些預設的屬性配置到 environment
// 2、啟用 profiles
configureEnvironment(environment, applicationArguments.getSourceArgs());
// 釋出 ApplicationEnvironmentPreparedEvent 事件
listeners.environmentPrepared(environment);
// 繫結 SpringApplication 環境
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader())
.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
}
// 附加的解析器將動態跟蹤底層 Environment 屬性源的任何新增或刪除
ConfigurationPropertySources.attach(environment);
return environment;
}
複製程式碼
這裡面做的事情就是將我們的配置,包括系統配置、application.properties、-D 引數等等統統打包給 environment。在 Spring 中,我們最常見的 xml 中使用的 ${xxx}
或者程式碼中使用的 @Value("${xxxx}")
等,最後都是從 environment 中拿值的。
這裡需要關注的一個比較重要的點是釋出 ApplicationEnvironmentPreparedEvent 事件,我們可以通過監聽這個事件來修改 environment。這裡可以參考下 SOFATracer 中 SofaTracerConfigurationListener 是如何利用這個事件來做環境配置處理的。
prepareContext 的處理過程
prepareContext 的處理過程中可以利用的點是非常多的,比如 ApplicationContextInitializer 的執行、ApplicationContextInitializedEvent 和 ApplicationPreparedEvent 事件釋出。
private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
// 設定 environment 給 context,所以需要注意的是,在此之前拿到的 context 中,environment 是沒有的。
context.setEnvironment(environment);
// 對 ApplicationContext 的後置處理,比如註冊 BeanNameGenerator 和 ResourceLoader
postProcessApplicationContext(context);
// 這裡開始執行所有的 ApplicationContextInitializer
applyInitializers(context);
// 釋出 ApplicationContextInitializedEvent 事件
listeners.contextPrepared(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
}
if (beanFactory instanceof DefaultListableBeanFactory) {
// 是否允許 bean 覆蓋,這裡如果是 false ,則可能會導致 BeanDefinitionOverrideException 異常
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
// Load the sources
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
load(context, sources.toArray(new Object[0]));
// 釋出 ApplicationPreparedEvent 事件
listeners.contextLoaded(context);
}
複製程式碼
ApplicationContextInitializer 是 spring 容器重新整理之前初始化 Spring ConfigurableApplicationContext 的回撥介面,ApplicationContextInitializer 的 initialize 方法執行之前,context 是還沒有重新整理的。可以看到在 applyInitializers 之後緊接著釋出了 ApplicationContextInitializedEvent 事件。其實這兩個點都可以對 context 搞一些事情,ApplicationContextInitializer 更純粹些,它只關注 context;而 ApplicationContextInitializedEvent 事件源中除了 context 之外,還有 springApplication 物件和引數 args。
prepareContext 最後階段是釋出了 ApplicationPreparedEvent 事件,表示上下文已經準備好了,可以隨時執行 refresh 了。
refreshContext 的處理過程
refreshContext 是 Spring 上下文重新整理的過程,這裡實際呼叫的是 AbstractApplicationContext 的 refresh 方法;所以 SpringBoot 也是複用了 Spring 上下文重新整理的過程。
@Override
public void refresh() throws BeansException, IllegalStateException {
// 加鎖處理
synchronized (this.startupShutdownMonitor) {
// 準備重新整理此上下文。主要包括佔位符的替換及驗證所有的 properties
prepareRefresh();
// 這裡做了很多事情:
// 1、讓子類重新整理內部beanFactory ,建立IoC容器(DefaultListableBeanFactory--ConfigurableListableBeanFactory 的實現類)
// 2、載入解析XML檔案(最終儲存到Document物件中)
// 3、讀取Document物件,並完成BeanDefinition的載入和註冊工作
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// 對 beanFactory 進行一些預處理(設定一些公共屬性)
prepareBeanFactory(beanFactory);
try {
// 允許在 AbstractApplicationContext的子類中對 BeanFactory 進行後置處理,postProcessBeanFactory()這個方法是個空實現。
postProcessBeanFactory(beanFactory);
// 呼叫 BeanFactoryPostProcessor 後置處理器處理 BeanFactory 例項(BeanDefinition)
invokeBeanFactoryPostProcessors(beanFactory);
// 註冊BeanPostProcessor後置處理器,BeanPostProcessors後置處理器用於攔截bean的建立
// 用於對建立後的bean例項進行處理
registerBeanPostProcessors(beanFactory);
// 初始化訊息資源
initMessageSource();
// 初始化應用事件廣播器
initApplicationEventMulticaster();
// 初始化特殊的bean,這個方法是空實現,讓AbstractApplicationContext的子類重寫
onRefresh();
// 註冊監聽器(ApplicationListener)
registerListeners();
// 例項化剩餘的單例bean(非懶載入方式), Bean的 IoC、DI 和 AOP 都是發生在此步驟
finishBeanFactoryInitialization(beanFactory);
// 完成重新整理
// 1、釋出 ContextRefreshedEvent 事件
// 2、處理 LifecycleProcessor
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// 銷燬已經建立的單例以避免資源懸空。
destroyBeans();
// 重置 ”active“ 標記
cancelRefresh(ex);
throw ex;
}
finally {
// 重置Spring核心中的常用自檢快取,清空單例bean內快取
resetCommonCaches();
}
}
}
複製程式碼
這個過程涉及到的東西非常多,可擴充套件的點也非常多,包括 BeanFactoryPostProcessor 處理、BeanPostProcessor 處理、LifecycleProcessor 處理已經 釋出 ContextRefreshedEvent 事件等。到這裡容器重新整理已經完成,容器已經 ready,DI 和 AOP 也已經完成。
BeanFactoryPostProcessor 處理
BeanFactoryPostProcessor 可以對我們的 beanFactory 內所有的 beandefinition(未例項化)資料進行修改,這個過程是在 bean 還沒有例項化之前做的。所以在這,我們通過自己去註冊一些 beandefinition ,也可以對 beandefinition 做一些修改。關於 BeanFactoryPostProcessor 的用法在很多框架中都有體現,這裡以 SOFATracer 中修改 Datasource 為例來說明下。
SOFATracer 中為了對有所基於 jdbc 規範的資料來源進行埋點,提供了一個 DataSourceBeanFactoryPostProcessor,用於修改原生 DataSource 來實現一層代理。程式碼詳見:com.alipay.sofa.tracer.boot.datasource.processor.DataSourceBeanFactoryPostProcessor
這裡只看核心程式碼部分,在 postProcessBeanFactory 方法中會根據 Datasource 的型別來建立不同的 DataSourceProxy;建立 DataSourceProxy 的過程就是修改原生 Datasource 的過程。
private void createDataSourceProxy(ConfigurableListableBeanFactory beanFactory,
String beanName, BeanDefinition originDataSource,
String jdbcUrl) {
// re-register origin datasource bean
BeanDefinitionRegistry beanDefinitionRegistry = (BeanDefinitionRegistry) beanFactory;
// 先把之前已經存在的 Datasource 的 BeanDefinition 移除
beanDefinitionRegistry.removeBeanDefinition(beanName);
boolean isPrimary = originDataSource.isPrimary();
originDataSource.setPrimary(false);
// 換個 beanName ,重新註冊到容器中
beanDefinitionRegistry.registerBeanDefinition(transformDatasourceBeanName(beanName),
originDataSource);
// 構建代理的 datasource BeanDefinition,型別為 SmartDataSource
RootBeanDefinition proxiedBeanDefinition = new RootBeanDefinition(SmartDataSource.class);
// 設定 BeanDefinition 相關屬性
proxiedBeanDefinition.setRole(BeanDefinition.ROLE_APPLICATION);
proxiedBeanDefinition.setPrimary(isPrimary);
proxiedBeanDefinition.setInitMethodName("init");
proxiedBeanDefinition.setDependsOn(transformDatasourceBeanName(beanName));
// 獲取原生 datasource 的屬性值
MutablePropertyValues originValues = originDataSource.getPropertyValues();
MutablePropertyValues values = new MutablePropertyValues();
String appName = environment.getProperty(TRACER_APPNAME_KEY);
// 修改和新增屬性
Assert.isTrue(!StringUtils.isBlank(appName), TRACER_APPNAME_KEY + " must be configured!");
values.add("appName", appName);
values.add("delegate", new RuntimeBeanReference(transformDatasourceBeanName(beanName)));
values.add("dbType",
DataSourceUtils.resolveDbTypeFromUrl(unwrapPropertyValue(originValues.get(jdbcUrl))));
values.add("database",
DataSourceUtils.resolveDatabaseFromUrl(unwrapPropertyValue(originValues.get(jdbcUrl))));
// 將新的 values 設定給代理 BeanDefinition
proxiedBeanDefinition.setPropertyValues(values);
// 將代理的 datasource BeanDefinition 註冊到容器中
beanDefinitionRegistry.registerBeanDefinition(beanName, proxiedBeanDefinition);
}
複製程式碼
上面這段程式碼就是 BeanFactoryPostProcessor 一種典型的應用場景,就是修改 BeanDefinition。
BeanFactoryPostProcessor 處理過程程式碼比較長,這裡就不在具體分析處理的流程。需要關注的點是:1、BeanFactoryPostProcessor 的作用,它能做哪些事情;2、它是在容器啟動的哪個階段執行的。
registerBeanPostProcessors 的處理過程
registerBeanPostProcessors 是用於註冊 BeanPostProcessor 的。BeanPostProcessor 的作用時機相對於 BeanFactoryPostProcessor 來說要晚一些,BeanFactoryPostProcessor 處理的是 BeanDefinition,Bean 還沒有例項化;BeanPostProcessor 處理的是 Bean,BeanPostProcessor 包括兩個方法,分別用於在 Bean 例項化之前和例項化之後回撥。
開篇有提到,在某些場景下會出現 BeanPostProcessor 不生效。對於 Spring 來說,BeanPostProcessor 本身也會被註冊成一個 Bean,那麼自然就可能會出現,BeanPostProcessor 處理的 bean 在 BeanPostProcessor 本身初始化之前就已經完成了的情況。
registerBeanPostProcessors 大體分為以下幾個部分:
- 註冊 BeanPostProcessorChecker。(當一個 bean 在 BeanPostProcessor 例項化過程中被建立時,即當一個bean沒有資格被所有 BeanPostProcessor 處理時,它記錄一個資訊訊息)
- 實現優先排序、排序和其他操作的 BeanPostProcessor 之間進行排序
- 註冊實現 PriorityOrdered 的 BeanPostProcessor
- 註冊實現 Ordered 的
- 註冊所有常規的 BeanPostProcessor
- 重新註冊所有的內部 BeanPostProcessor
- 將後處理器註冊為用於檢測內部 bean 的 applicationlistener,將其移動到處理器鏈的末端(用於獲取代理等)。
這裡還是以擴充套件時機為主線,Bean 的 IoC、DI 和 AOP 初始化過程不細究。
LifecycleProcessor 的處理過程
LifecycleProcessor 的處理過程是在 finishRefresh 方法中執行,下面先看下 finishRefresh 方法:
protected void finishRefresh() {
// 清除上下文級的資源快取(比如掃描的ASM後設資料)。
clearResourceCaches();
// 為此上下文初始化 LifecycleProcessor。
initLifecycleProcessor();
// 首先將 refresh 傳播到 LifecycleProcessor。
getLifecycleProcessor().onRefresh();
// 釋出 ContextRefreshedEvent 事件
publishEvent(new ContextRefreshedEvent(this));
// Participate in LiveBeansView MBean, if active.
LiveBeansView.registerApplicationContext(this);
}
複製程式碼
初始化 initLifecycleProcessor 是從容器中拿到所有的 LifecycleProcessor ,如果業務程式碼中沒有實現 LifecycleProcessor 介面的 bean ,則使用預設的 DefaultLifecycleProcessor。
onRefresh 過程是 最後會呼叫到 Lifecycle 介面的 start 方法。LifeCycle 定義 Spring 容器物件的生命週期,任何 spring 管理物件都可以實現該介面。然後,當 ApplicationContext 本身接收啟動和停止訊號(例如在執行時停止/重啟場景)時,spring 容器將在容器上下文中找出所有實現了 LifeCycle 及其子類介面的類,並一一呼叫它們實現的類。spring 是通過委託給生命週期處理器 LifecycleProcessor 來實現這一點的。Lifecycle 介面定義如下:
public interface Lifecycle {
/**
* 啟動當前元件
* 1、如果元件已經在執行,不應該丟擲異常
* 2、對於容器,這將把開始訊號傳播到應用的所有元件
*/
void start();
/**
* 通常以同步方式停止該元件,當該方法執行完成後,該元件會被完全停止。當需要非同步停止行為時,考慮實現 SmartLifecycle 和它的 stop
* (Runnable) 方法變體。注意,此停止通知在銷燬前不能保證到達:在常規關閉時,{@code Lifecycle} bean將首先收到一個停止通知,然後才傳播
* 常規銷燬回撥;然而,在上下文的生命週期內的熱重新整理或中止的重新整理嘗試上,只呼叫銷燬方法。對於容器,這將把停止訊號傳播到應用的所有元件
*/
void stop();
/**
* 檢查此元件是否正在執行。
* 1. 只有該方法返回 false 時,start方法才會被執行。
* 2. 只有該方法返回 true 時,stop(Runnable callback) 或 stop() 方法才會被執行。
*/
boolean isRunning();
}
複製程式碼
至此,容器重新整理其實已經就完成了。可以看到 Spring 或者 SpringBoot 在整個啟動過程中,有非常多的口子暴露出來,供使用者使用,非常靈活。
異常處理邏輯
與正常流程類似,異常處理流程同樣作為 SpringBoot 生命週期的一個環節,在異常發生時,會通過一些機制來處理收尾過程。異常處理部分 SpringBoot 1.x 版本和 SpringBoot 2.x 版本差異還是比較大的。這裡只分析 SpringBoot 2.x 的處理過程。這裡直接貼一段程式碼:
private void handleRunFailure(ConfigurableApplicationContext context,
Throwable exception,
Collection<SpringBootExceptionReporter> exceptionReporters,
SpringApplicationRunListeners listeners) {
try {
try {
// exitCode
handleExitCode(context, exception);
if (listeners != null) {
// failed
listeners.failed(context, exception);
}
}
finally {
// 這裡也是擴充套件的口子
reportFailure(exceptionReporters, exception);
if (context != null) {
context.close();
}
}
}
catch (Exception ex) {
logger.warn("Unable to close ApplicationContext", ex);
}
ReflectionUtils.rethrowRuntimeException(exception);
}
複製程式碼
上述程式碼片段主要做了以下幾件事:
- handleExitCode: 這裡會拿到異常的 exitCode,隨後釋出一個 ExitCodeEvent 事件,最後交由 SpringBootExceptionHandler 處理。
- SpringApplicationRunListeners#failed: 迴圈遍歷呼叫所有 SpringApplicationRunListener 的 failed 方法
- reportFailure:使用者可以自定義擴充套件 SpringBootExceptionReporter 介面來實現定製化的異常上報邏輯
在 SpringApplicationRunListeners#failed 中,業務產生的異常將直接被丟擲,而不會影響異常處理的主流程。
總結
至此,SpringBoot 啟動的主流程已經全部分析完成了。從擴充套件和擴充套件時機的角度來看,整個過程中,SpringBoot 提供了非常多的擴充套件口子,讓使用者可以在容器啟動的各個階段(無論是啟動,環境準備,容器重新整理等等)做一些定製化的操作。使用者可以利用這些擴充套件介面來修改 bean 、修改環境變數,給使用者極大的空間。