Серия Springboot — процесс запуска

Spring Boot Spring

Публичный аккаунт WeChat:студия gmapper
Колонка самородков:glmapper
Вейбо:Сумасшедший Stone_henu
Добро пожаловать, чтобы следить, учиться и делиться вместе

Будучи очень популярной микросервисной платформой, SpringBoot позволяет очень просто создавать независимые приложения производственного уровня Spring, поэтому его предпочитают многие интернет-компании.

Рекомендуемое чтение

задний план

Недавно написаноSOFATracerВ процессе интеграции Spring Cloud Stream RocketMQ мы столкнулись с некоторыми проблемами, такими как: BeanPostProcessor не вступает в силу, как изменить Bean, когда BeanPostProcessor не вступает в силу и т. д. Эти проблемы на самом деле связаны с жизненным циклом Bean, и, конечно, процесс запуска контейнера связан. Процесс запуска SpringBoot мне не чужд, и можно сказать относительно знаком, но я до конца не разбирался в этом пункте раньше, и неизбежно наступать на какие-то ямки в реальном процессе приложения. Кроме того, помните, что я написалСерия SpringBoot — принцип запуска FatJar, просто следуя предыдущей статье, продолжайте изучать некоторые моменты знаний в SpringBoot.

Примечание: эта статья основана на версии SpringBoot 2.1.0.RELEASE Между различными версиями SpringBoot могут быть различия, но общий процесс в основном одинаков.

загрузочная запись

в этомСерия SpringBoot — принцип запуска FatJarКак показано в статье, JarLaunch, наконец, создает объект экземпляра MainMethodRunner, а затем вызывает основной метод в классе BootStrap посредством отражения. «Основной метод в классе BootStrap» здесь на самом деле является бизнес-записью SpringBoot, которая является общей. фрагмент кода:

@SpringBootApplication
public class GlmapperApplication {
    public static void main(String[] args) {
        SpringApplication.run(GlmapperApplication.class, args);
    }
}

Из кода можно интуитивно понять, что запуск осуществляется путем вызова статического метода run приложения SpringApplication; этот метод запуска фактически создает экземпляр SpringApplication, а затем вызывает метод запуска этого экземпляра для запуска 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 и процессом выполнения метода run SpringApplication.

Создание экземпляра 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 состоит в том, чтобы использовать полное имя интерфейса в качестве ключа и полное имя класса реализации в качестве значения для записи в файле META-INF/spring.factories проекта, а затем предоставить статические методы через SpringFactoriesLoader. инструментальный класс для загрузки и кэширования классов. spring.factories — это основной файл конфигурации Spring Boot. SpringFactoriesLoader можно понимать как реализацию расширения spi, предоставляемую самой Spring. Конфигурация spring.factories по умолчанию, представленная в SpringBoot, выглядит следующим образом:

# 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

запустить метод основного процесса

Вот интуитивно посмотрите на код, а затем проанализируйте его один за другим:

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. Время и последовательность выполнения слушателей
  • 5. Логика обработки исключений

Очень подробный анализ времени выполнения и порядка прослушивателей был сделан в предыдущих статьях, см.:Серия SpringBoot - подробное объяснение механизма событий. Остальные четыре пункта подробно проанализированы ниже.

Анализ процесса запуска — это, по сути, понимание всего жизненного цикла контейнера, включая время прослушивателей, выполняющих различные события, время выполнения постпроцессора, время готовности среды и так далее. Освоение этих расширений и таймингов может многое сделать для реального развития бизнеса.

Процесс подготовки среды

Процесс prepareEnvironment является относительно ранним, и основная цель здесь - предоставить среду для обновления контекста.

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 и т. д., в среду. В Spring наше наиболее распространенное использование xml${xxx}или используется в коде@Value("${xxxx}")и так далее, и, наконец, получить значение из среды.

Здесь важно отметить, чтоСообщение о событии ApplicationEnvironmentPreparedEvent, мы можем прослушивать это событиеИзменить среду. Здесь вы можете обратиться к SOFATracerSofaTracerConfigurationListenerКак использовать это событие для обработки конфигурации среды.

Процесс препарата

Есть много моментов, которые можно использовать при обработке 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 ConfigurableApplicationContext перед обновлением контейнера spring.До выполнения метода инициализации ApplicationContextInitializer контекст не обновляется. Вы можете видеть, что событие ApplicationContextInitializedEvent публикуется сразу после applyInitializers. На самом деле эти две точки могут что-то делать с контекстом, ApplicationContextInitializer более чистый, он обращает внимание только на контекст, а источник событий ApplicationContextInitializedEvent имеет помимо контекста объект springApplication и параметры args.

Последним этапом prepareContext является публикация события ApplicationPreparedEvent, указывающего, что контекст готов и может быть обновлен в любое время.

Процесс обновления контекста

refreshContext — это процесс обновления контекста Spring, и здесь фактически вызывается метод обновления AbstractApplicationContext, поэтому 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, которые были выпущены. На этом этапе обновление контейнера завершено, контейнер готов, DI и AOP также завершены.

Обработка BeanfactoryPostProcessor

BeanFactoryPostProcessor может изменять все данные beandefinition (неэкземпляры) в нашей beanFactory до создания экземпляра bean-компонента. Итак, здесь мы можем сами зарегистрировать некоторое определение bean-компонента, а также внести некоторые изменения в определение bean-компонента. Использование BeanFactoryPostProcessor отражено во многих фреймворках.Вот пример изменения источника данных в SOFATracer.

Чтобы скрыть некоторые источники данных, основанные на спецификации jdbc, SOFATracer предоставляет DataSourceBeanFactoryPostProcessor для изменения собственного источника данных для реализации слоя прокси. Подробности смотрите в коде:com.alipay.sofa.tracer.boot.datasource.processor.DataSourceBeanFactoryPostProcessor

Посмотрите здесь только на основную часть кода.В методе postProcessBeanFactory будут созданы разные DataSourceProxy в соответствии с типом источника данных; процесс создания DataSourceProxy — это процесс изменения исходного источника данных.

 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 используется для регистрации BeanPostProcessors. Время бобостногопроцессора позже, чем у BunterfactoryPostProcessor. BeanffactoryPostProcessor обрабатывает GnerDefinition, а боб не был создан; BeanPostProcessor обрабатывает фасоль, а BeanPostProcessor включает в себя два метода, которые используются для возникновения досмотра до и после призваности.,

Как упоминалось в начале, BeanPostProcessor может не работать в некоторых сценариях. Для Spring сам BeanPostProcessor также регистрируется как Bean, поэтому вполне естественно, что bean-компонент, обработанный BeanPostProcessor, был завершен до инициализации самого BeanPostProcessor.

registerBeanPostProcessors примерно разделен на следующие части:

  • Зарегистрируйте BeanPostProcessorChecker. (Когда bean-компонент создается во время создания экземпляра BeanPostProcessor, т. е. когда bean-компонент не может быть обработан всеми BeanPostProcessor, он регистрирует информационное сообщение)
  • Сортировка между BeanPostProcessors, реализующими приоритезацию, сортировку и другие операции
  • Зарегистрируйте BeanPostProcessor, который реализует PriorityOrdered
  • Зарегистрировать выполнение Заказа
  • Зарегистрируйте все обычные BeanPostProcessors
  • Перерегистрируйте все внутренние BeanPostProcessors
  • Зарегистрируйте постпроцессор как applicationlistener для обнаружения внутренних компонентов, переместите его в конец цепочки процессоров (для получения прокси и т. д.).

Здесь мы по-прежнему фокусируемся на времени расширения, а процесс инициализации IoC, DI и AOP Bean тщательно не изучается.

Обработка 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, используется DefaultLifecycleProcessor по умолчанию.

Процесс onRefresh — это последний вызов метода start интерфейса Lifecycle. LifeCycle определяет жизненный цикл объектов-контейнеров Spring, и любой управляемый объект Spring может реализовать этот интерфейс. Затем, когда сам ApplicationContext получает сигналы запуска и остановки (например, сценарии остановки/перезапуска во время выполнения), контейнер Spring обнаружит все классы, реализующие интерфейсы LifeCycle и его подклассов в контексте контейнера, и вызовет их один за другим. , вид. Spring делает это, делегируя LifecycleProcessor. Интерфейс жизненного цикла определяется следующим образом:

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: Здесь будет получен аномальный код выхода, а затем будет выпущено событие ExitCodeEvent, которое будет обработано SpringBootExceptionHandler.
  • SpringApplicationrunListeners # Не удалось: петлю через неисправные методы вызова всех пружинных плиткулеров
  • reportFailure: пользователи могут настраивать и расширять интерфейс SpringBootExceptionReporter для реализации настраиваемой логики отчетов об исключениях.

В SpringApplicationRunListeners#failed исключения, сгенерированные бизнесом, будут создаваться напрямую, не затрагивая основной процесс обработки исключений.

Суммировать

На данный момент полностью проанализирован основной процесс, запускаемый SpringBoot. С точки зрения расширения и времени расширения, на протяжении всего процесса SpringBoot предоставляет множество отверстий для расширения, позволяя пользователям выполнять некоторые индивидуальные настройки на различных этапах запуска контейнера (будь то запуск, подготовка среды, обновление контейнера и т. д.). , действовать. Пользователи могут использовать эти расширенные интерфейсы для изменения bean-компонентов и переменных среды, предоставляя пользователям много места.