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 、修改环境变量,给用户极大的空间。

相关文章
相关标签/搜索