SpringBoot 系列-啓動過程


微信公衆號:glmapper工做室
掘金專欄:glmapper
微 博:瘋狂的石頭_henu
歡迎關注,一塊兒學習、一塊兒分享java

SpringBoot 做爲目前很是流行的微服務框架,它使得構建獨立的 Spring 生產級應用變得很是簡單,所以受到不少互聯網企業的青睞。git

推薦閱讀

背景

最近在寫 SOFATracer 集成 Spring Cloud Stream RocketMQ 的過程當中,遇到了一些問題,好比:BeanPostProcessor 不生效,如何在 BeanPostProcessor 不生效的狀況下去修改一個 Bean 等,這些問題其實都是和 Bean 的生命週期有關係的,固然也和容器啓動的過程有關係。SpringBoot 的啓動過程對於我來講其實不算陌生,也能夠說是比較熟悉,可是以前沒有完整的梳理過這一款的東西,再實際的應用過程成不免再去踩一些坑。另外想到以前也寫過一篇 SpringBoot系列- FatJar 啓動原理,恰好承接上篇,繼續來探索 SpringBoot 中的一些知識點。github

注:本篇基於 SpringBoot 2.1.0.RELEASE 版本,SpringBoot 各個版本之間可能存在差別,不過大致流程基本差很少,因此各位看官在實際的工做過程當中也web

啓動入口

在這篇SpringBoot系列- FatJar 啓動原理 文章中介紹獲得,JarLaunch 最後是構建了一個 MainMethodRunner 實例對象,而後經過反射的方式調用了 BootStrap 類中的 main 方法,這裏的 ’BootStrap 類中的 main 方法‘ 實際上就是 SpringBoot 的業務入口,也就是常見的下面的代碼片斷:spring

@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 方法執行過程便可。tomcat

SpringApplication 實例的構建

篇幅緣由,咱們只分析核心的構建流程。springboot

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();
}
複製代碼

上面代碼段中,須要關注兩個點:微信

  • 一、初始化 ApplicationContextInitializer;
  • 二、初始化 ApplicationListener

要注意的是這裏的實例化,並不是是經過註解和掃包完成,而是經過一種不依賴 Spring 上下文的加載方法;這種作法是爲了可以使得在 Spring 完成啓動前作各類配置。Spring 的解決方法是以接口的全限定名做爲 key,實現類的全限定名做爲 value 記錄在項目的 META-INF/spring.factories 文件中,而後經過SpringFactoriesLoader 工具類提供靜態方法進行類加載並緩存下來,spring.factories 是Spring Boot 的核心配置文件。SpringFactoriesLoader 能夠理解爲 Spring 本身提供的一種 spi 擴展實現。SpringBoot 中提供的默認的 spring.factories 配置以下:app

# 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;
}
複製代碼

上面對代碼基本都作了一些簡單的註釋,有幾個須要關注的點:

  • 一、prepareEnvironment 的處理過程
  • 二、prepareContext 的處理過程
  • 三、refreshContext 的處理過程
  • 四、listeners 執行時機及順序
  • 五、異常處理邏輯

關於 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
    // 一、將參數和一些默認的屬性配置到 environment
    // 二、激活 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();
        // 這裏作了不少事情:
        // 一、讓子類刷新內部beanFactory ,建立IoC容器(DefaultListableBeanFactory--ConfigurableListableBeanFactory 的實現類)
        // 二、加載解析XML文件(最終存儲到Document對象中)
        // 三、讀取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);
            // 完成刷新
            // 一、發佈 ContextRefreshedEvent 事件
            // 二、處理 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 處理過程代碼比較長,這裏就不在具體分析處理的流程。須要關注的點是:一、BeanFactoryPostProcessor 的做用,它能作哪些事情;二、它是在容器啓動的哪一個階段執行的。

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 {
    /**
     * 啓動當前組件
     * 一、若是組件已經在運行,不該該拋出異常
     * 二、對於容器,這將把開始信號傳播到應用的全部組件
     */

    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 、修改環境變量,給用戶極大的空間。

相關文章
相關標籤/搜索