1. 程式人生 > 其它 >SpringBoot應用--01-啟動流程解析

SpringBoot應用--01-啟動流程解析

1. 一個簡單SpringBoot專案主啟動類配置

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

2. SpringApplication#run方法總體流程

    public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
        
return run(new Class<?>[] { primarySource }, args); } public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) { return new SpringApplication(primarySources).run(args); }

  2.1 SpringApplication構造方法

    public SpringApplication(Class<?>... primarySources) {
        
this(null, primarySources); }
@SuppressWarnings({ "unchecked", "rawtypes" }) public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) { this.resourceLoader = resourceLoader; Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources)); this.webApplicationType = WebApplicationType.deduceFromClasspath();
      // 從spring.factories檔案中獲取應用初始化器 通過反射獲取並注入當前Boot應用中 setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.
class));
      // 從spring.factories檔案中獲取應用監聽器 通過反射獲取並注入當前Boot應用中 setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.
class));
      // 推導主啟動類
this.mainApplicationClass = deduceMainApplicationClass(); }

SpringApplication#getSpringFactoriesInstances方法追蹤:
    /** 獲取定義在spring.factories檔案中的元件 */
    private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
        return getSpringFactoriesInstances(type, new Class<?>[] {});
    }

    private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        // Use names and ensure unique to protect against duplicates
        Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
        List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
        AnnotationAwareOrderComparator.sort(instances);
        return instances;
    }

    /* 使用反射機制生成spring.factories檔案中的元件  */
    @SuppressWarnings("unchecked")
    private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
            ClassLoader classLoader, Object[] args, Set<String> names) {
        List<T> instances = new ArrayList<>(names.size());
        for (String name : names) {
            try {
                Class<?> instanceClass = ClassUtils.forName(name, classLoader);
                Assert.isAssignable(type, instanceClass);
                Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
                T instance = (T) BeanUtils.instantiateClass(constructor, args);
                instances.add(instance);
            } catch (Throwable ex) {
                throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
            }
        }
        return instances;
    }
SpringFactoriesLoader.loadFactoryNames方法詳情:解析spring.factories檔案中資料讀入Properties中
SpringApplication#deduceMainApplicationClass方法追蹤:獲取當前執行java方法棧中名稱為main的方法對應的類


  private Class<?> deduceMainApplicationClass() {
        try {
            StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
            for (StackTraceElement stackTraceElement : stackTrace) {
                if ("main".equals(stackTraceElement.getMethodName())) {
                    return Class.forName(stackTraceElement.getClassName());
                }
            }
        } catch (ClassNotFoundException ex) {
            // Swallow and continue
        }
        return null;
    }

PS: spring.factories檔案一般由spring-boot-autoconfigure 子模組中的WEB-INF目錄下,是一種SPI機制的實現方式,spring-boot-autoconfigure是SpringBoot實現自動裝配的基礎,同時也是spring-boot-starter的依賴,間接屬於各個starter的簡介依賴

  2.2 SpringApplication#run方法總覽

  /**
     * Run the Spring application, creating and refreshing a new {@link ApplicationContext}.
     * -- 啟動Spring應用,建立並且重新整理ApplicationContext
     * @param args the application arguments (usually passed from a Java main method)
     * @return a running {@link ApplicationContext}
     */
    public ConfigurableApplicationContext run(String... args) {
        /** 1. 建立一個 StopWatch,允許計時的一些任務, 公開每個命名任務的總執行時間和執行時間啟動任務計時器 */
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();

        ConfigurableApplicationContext context = null;
        // 存放異常報告類
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();

        /**
         * 2. 配置java.awt.headless為true -- 模式切換
         *     怎麼從正常模式切換到headline模式呢, 其實很簡單,java提供了幾種方式:
         *         1、在main函式入口通過System.setProperties("java.awt.headless", true);
         *         2、通過java的啟動引數指定 JAVA_OPTS="-Djava.awt.headless=true"
         *         3、在環境變數中定義改屬性,並且賦值位true。 export java.awt.headless=true
         *   使用以上3種方法都可以讓jvm在啟動的時候,把工作模式修改位headline,這樣就不會在建立使用awt資源的
         *   時候去載入window互動系統提供的具體實現了
          */
        configureHeadlessProperty();

        /**
         * 3. 從當前classpath下獲取所有的META-INF/spring.factories檔案獲取所有的SpringApplicationRunListener——Spring執行監聽器
         * # Run Listeners
         * org.springframework.boot.SpringApplicationRunListener=\
         * org.springframework.boot.context.event.EventPublishingRunListener
         * 同時將執行監聽器啟動(預設配置使用EventPublishingRunListener),該方法釋出一個ApplicationStartingEvent事件
         */
        SpringApplicationRunListeners listeners = getRunListeners(args);
        listeners.starting();
        try {
            /*4. 建立一個預設的為SpringBoot應用啟動提供獲取引數的功能元件*/
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            /**
             * 5. 根據當前SpringBoot的web環境狀態建立一個符合的Environment
             *     Servlet 的Web環境下使用StandardServletEnvironment
             *  REACTIVE 的Web環境下使用StandardReactiveWebEnvironment
             *  非Web環境下使用StandardEnvironment
             * 同時使用applicationArguments獲取資源資訊
             */
            ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);

            // 6. 根據執行時環境資料spring.beaninfo.ignore值 配置是否需要忽略某些Bean
            configureIgnoreBeanInfo(environment);

            // 7. 列印Banner展示
            Banner printedBanner = printBanner(environment);

            /**
             * 8. 建立Spring ApplicationContext上下文
             *     SERVLET使用    org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext
             *  REACTIVE使用    org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext
             *     預設使用    org.springframework.context.annotation.AnnotationConfigApplicationContext
             *  使用反射機制生成類對應的物件
             */
            context = createApplicationContext();

            /**
             * 9. 從當前classpath下獲取所有的META-INF/spring.factories檔案
             * 從檔案獲取所有的SpringBootExceptionReporter Spring執行異常報告器
             *# Error Reporters
             * org.springframework.boot.SpringBootExceptionReporter=\
             * org.springframework.boot.diagnostics.FailureAnalyzers
             */
            exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
                    new Class[] { ConfigurableApplicationContext.class }, context);

            /* 10. 預準備Spring ApplicationContext上下文 */
            prepareContext(context, environment, listeners, applicationArguments, printedBanner);

            /* 11. 核心方法 ApplicationContext#refresh方法相關*/
            refreshContext(context);

            /* 12. IOC容器refresh之後的操作,目前該方法是空實現 */
            afterRefresh(context, applicationArguments);
            // 13. 停止監控器
            stopWatch.stop();
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
            }
            /* 14. 釋出IOC容器已經成功啟動的事件  */
            listeners.started(context);
            /*15. 指定自定義的CommandLineRunner ApplicationRunner*/
            callRunners(context, applicationArguments);
        } catch (Throwable ex) {
            /*16. 處理異常資訊  */
            handleRunFailure(context, ex, exceptionReporters, listeners);
            throw new IllegalStateException(ex);
        }
        try {
            listeners.running(context);
        } catch (Throwable ex) {
            handleRunFailure(context, ex, exceptionReporters, null);
            throw new IllegalStateException(ex);
        }
        return context;
    }

  2.3 SpringApplication#run方法拆解

1. 建立一個 StopWatch,允許計時的一些任務, 公開每個命名任務的總執行時間和執行時間啟動任務計時器
   public StopWatch() {  this(""); }
    public StopWatch(String id) {
        this.keepTaskList = true;
        this.taskList = new LinkedList();
        this.id = id;
    }
    public void start() throws IllegalStateException {   this.start("");  }
    public void start(String taskName) throws IllegalStateException {
        if (this.currentTaskName != null) {
            throw new IllegalStateException("Can't start StopWatch: it's already running");
        } else {
            this.currentTaskName = taskName;
            this.startTimeMillis = System.currentTimeMillis();
        }
    }
     2. 配置java.awt.headless為true -- 模式切換
/* 配置java.awt.headless,預設為true */
   private void configureHeadlessProperty() {
        System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS,System.getProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));
    }
    3. 從當前classpath下獲取所有的META-INF/spring.factories檔案獲取所有的SpringApplicationRunListener——Spring執行監聽器
  /* 獲取啟動監聽器 */
    private SpringApplicationRunListeners getRunListeners(String[] args) {
        Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
        return new SpringApplicationRunListeners(logger,getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
    }
    4. 建立一個預設的為SpringBoot應用啟動提供獲取引數的功能元件
  public DefaultApplicationArguments(String[] args) {
        Assert.notNull(args, "Args must not be null");
        this.source = new Source(args);
        this.args = args;
    }
    5. 根據當前SpringBoot的web環境狀態建立一個符合的Environment
  /* 預準備SpringBoot執行環境  */
    private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,  ApplicationArguments applicationArguments) {
        // Create and configure the environment
        ConfigurableEnvironment environment = getOrCreateEnvironment();
        // 配置執行時環境
        configureEnvironment(environment, applicationArguments.getSourceArgs());
        // 執行監聽器中注入執行環境
        listeners.environmentPrepared(environment);
        // 將執行環境繫結在SpringBoot上下文中
        bindToSpringApplication(environment);
        if (!this.isCustomEnvironment) {
            environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
                    deduceEnvironmentClass());
        }
        // 從執行環境中獲取配置資源附件
        ConfigurationPropertySources.attach(environment);
        return environment;
    }
getOrCreateEnvironment方法追蹤
    private ConfigurableEnvironment getOrCreateEnvironment() {
        if (this.environment != null) {  return this.environment;  }
        switch (this.webApplicationType) {
          case SERVLET:
              // Servlet 的Web環境下使用StandardServletEnvironment
              return new StandardServletEnvironment();
          case REACTIVE:
              // REACTIVE 的Web環境下使用StandardReactiveWebEnvironment
              return new StandardReactiveWebEnvironment();
          default:
              // 非Web環境下使用StandardEnvironment
              return new StandardEnvironment();
        }
    }
configureEnvironment方法追蹤
  protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
        configurePropertySources(environment, args);
        configureProfiles(environment, args);
    }

    /**
     * Add, remove or re-order any {@link PropertySource}s in this application's
     * environment.
     * -- 在此應用程式中新增、刪除或重新排序任何{@link PropertySource} 環境
     * @param environment this application's environment
     * @param args arguments passed to the {@code run} method
     * @see #configureEnvironment(ConfigurableEnvironment, String[])
     */
    protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
        // 執行時環境中宣告的屬性資源資訊
        MutablePropertySources sources = environment.getPropertySources();
        if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
            sources.addLast(new MapPropertySource("defaultProperties", this.defaultProperties));
        }
        if (this.addCommandLineProperties && args.length > 0) {
            String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
            if (sources.contains(name)) {
                PropertySource<?> source = sources.get(name);
                CompositePropertySource composite = new CompositePropertySource(name);
                composite.addPropertySource(
                        new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args));
                composite.addPropertySource(source);
                sources.replace(name, composite);
            } else {
                // 建立一個從命令列獲取配置屬性的處理器並新增在第一位
                sources.addFirst(new SimpleCommandLinePropertySource(args));
            }
        }
    }

    /**
     * Configure which profiles are active (or active by default) for this application
     * environment. Additional profiles may be activated during configuration file
     * processing via the {@code spring.profiles.active} property.
     * @param environment this application's environment
     * @param args arguments passed to the {@code run} method
     * @see #configureEnvironment(ConfigurableEnvironment, String[])
     * @see org.springframework.boot.context.config.ConfigFileApplicationListener
     */
    protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
        environment.getActiveProfiles(); // ensure they are initialized 獲取當前執行獲取所有處於活躍狀態的Profile
        // But these ones should go first (last wins in a property key clash)  進行去重操作
        Set<String> profiles = new LinkedHashSet<>(this.additionalProfiles);
        profiles.addAll(Arrays.asList(environment.getActiveProfiles()));
        environment.setActiveProfiles(StringUtils.toStringArray(profiles));
    }
bindToSpringApplication方法追蹤
  protected void bindToSpringApplication(ConfigurableEnvironment environment) {
        try {
            Binder.get(environment).bind("spring.main", Bindable.ofInstance(this));
        } catch (Exception ex) {
            throw new IllegalStateException("Cannot bind to SpringApplication", ex);
        }
    }
ConfigurationPropertySources.attach方法追蹤
  public static void attach(Environment environment) {
        Assert.isInstanceOf(ConfigurableEnvironment.class, environment);
        MutablePropertySources sources = ((ConfigurableEnvironment) environment).getPropertySources();
        PropertySource<?> attached = sources.get(ATTACHED_PROPERTY_SOURCE_NAME);
        if (attached != null && attached.getSource() != sources) {
            sources.remove(ATTACHED_PROPERTY_SOURCE_NAME);
            attached = null;
        }
        if (attached == null) {
            sources.addFirst(new ConfigurationPropertySourcesPropertySource(ATTACHED_PROPERTY_SOURCE_NAME,
                    new SpringConfigurationPropertySources(sources)));
        }
    }
    6. 根據執行時環境資料spring.beaninfo.ignore值 配置是否需要忽略某些Bean
  private void configureIgnoreBeanInfo(ConfigurableEnvironment environment) {
        if (System.getProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME) == null) {
            Boolean ignore = environment.getProperty("spring.beaninfo.ignore", Boolean.class, Boolean.TRUE);
            System.setProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME, ignore.toString());
        }
    }
    7. 列印Banner展示
    private Banner printBanner(ConfigurableEnvironment environment) {
        // 如果Banner模式使用關閉則不需要列印
        if (this.bannerMode == Banner.Mode.OFF) { return null; }
        ResourceLoader resourceLoader = (this.resourceLoader != null) ? this.resourceLoader : new DefaultResourceLoader(getClassLoader());
        SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(resourceLoader, this.banner);
        // 使用日誌模式則輸出在日誌中
        if (this.bannerMode == Mode.LOG) {
            return bannerPrinter.print(environment, this.mainApplicationClass, logger);
        }
        // 預設列印輸出在console控制檯中
        return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
    }
    8. 建立Spring ApplicationContext上下文
  protected ConfigurableApplicationContext createApplicationContext() {
        Class<?> contextClass = this.applicationContextClass;
        if (contextClass == null) {
            try {
                switch (this.webApplicationType) {
                case SERVLET:
                    //  預設使用    org.springframework.context.annotation.AnnotationConfigApplicationContext
                    // SERVLET使用    org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext
                    // REACTIVE使用    org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext
                    contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
                    break;
                case REACTIVE:
                    contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
                    break;
                default:
                    contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
                }
            } catch (ClassNotFoundException ex) {
                throw new IllegalStateException("Unable create a default ApplicationContext, " + "please specify an ApplicationContextClass",ex);
            }
        }
        return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
    }
    9. 從當前classpath下獲取所有的META-INF/spring.factories檔案,從檔案獲取所有的SpringBootExceptionReporter Spring執行異常報告器
    private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        // Use names and ensure unique to protect against duplicates
        Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
        List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
        AnnotationAwareOrderComparator.sort(instances);
        return instances;
    }
    10. 預準備Spring ApplicationContext上下文
  private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
            SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
        /**
         * 為當前ApplicationContext注入
         *  environment            ---- 執行時環境
         *     beanNameGenerator   ---- Bean名稱生成器
         *     resourceLoader      ---- 資源載入器
         *     classLoader         ---- 類載入器
         */
        context.setEnvironment(environment);
        postProcessApplicationContext(context);

        /**
         * 構造器方法從當前classpath下獲取所有的META-INF/spring.factories檔案,獲取ApplicationContextInitializer對應資料解析成 this.initializers
         * # Application Context Initializers
         *     org.springframework.context.ApplicationContextInitializer=\
         *         org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
         *         org.springframework.boot.context.ContextIdApplicationContextInitializer,\
         *         org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
         *         org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer
         *         此處執行該this.initializers中的所有初始化方法
         * 執行時監控器釋出上下文已準備事件
         */
        applyInitializers(context);
        listeners.contextPrepared(context);

        // 如果記錄啟動日誌的話記錄啟動+當前活躍Profile資訊
        if (this.logStartupInfo) {
            logStartupInfo(context.getParent() == null);
            logStartupProfileInfo(context);
        }

        // Add boot specific singleton beans   新增引導特定的單例bean
        context.getBeanFactory().registerSingleton("springApplicationArguments", applicationArguments);
        if (printedBanner != null) {
            context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
        }
        /**
         * Load the sources 獲取所有資源配置
         * 將所有Bean定義載入到ApplicationContext上下文中
         * 執行時監控器釋出上下文已載入事件
         */
        Set<Object> sources = getAllSources();
        Assert.notEmpty(sources, "Sources must not be empty");
        load(context, sources.toArray(new Object[0]));
        listeners.contextLoaded(context);
    }
    11. 核心方法 ApplicationContext#refresh方法相關
    private void refreshContext(ConfigurableApplicationContext context) {
        refresh(context);
        if (this.registerShutdownHook) {
            try {
                context.registerShutdownHook();
            } catch (AccessControlException ex) {
                // Not allowed in some environments.
            }
        }
    }
  protected void refresh(ApplicationContext applicationContext) {
        Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
        ((AbstractApplicationContext) applicationContext).refresh();
    } 
  AbstractApplicationContext.refresh()單獨解析流程
    12. IOC容器refresh之後的操作,目前該方法是空實現
  protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) { }
    13. 停止監控器
  public void stop() throws IllegalStateException {
        if (this.currentTaskName == null) {
            throw new IllegalStateException("Can't stop StopWatch: it's not running");
        } else {
            long lastTime = System.currentTimeMillis() - this.startTimeMillis;
            this.totalTimeMillis += lastTime;
            this.lastTaskInfo = new StopWatch.TaskInfo(this.currentTaskName, lastTime);
            if (this.keepTaskList) {
                this.taskList.add(this.lastTaskInfo);
            }

            ++this.taskCount;
            this.currentTaskName = null;
        }
    }
    14. 釋出IOC容器成功啟動的事件
    15.指定自定義的CommandLineRunner ApplicationRunner
  /* 呼叫所有自定義的ApplicationRunner+CommandLineRunner 執行對應的業務方法*/
    private void callRunners(ApplicationContext context, ApplicationArguments args) {
        List<Object> runners = new ArrayList<>();
        runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
        runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
        AnnotationAwareOrderComparator.sort(runners);
        for (Object runner : new LinkedHashSet<>(runners)) {
            if (runner instanceof ApplicationRunner) {
                callRunner((ApplicationRunner) runner, args);
            }
            if (runner instanceof CommandLineRunner) {
                callRunner((CommandLineRunner) runner, args);
            }
        }
    }
    private void callRunner(ApplicationRunner runner, ApplicationArguments args) {
        try {
            (runner).run(args);
        } catch (Exception ex) {
            throw new IllegalStateException("Failed to execute ApplicationRunner", ex);
        }
    }
    private void callRunner(CommandLineRunner runner, ApplicationArguments args) {
        try {
            (runner).run(args.getSourceArgs());
        } catch (Exception ex) {
            throw new IllegalStateException("Failed to execute CommandLineRunner", ex);
        }
    }
    16. 啟動異常處理
  private void handleRunFailure(ConfigurableApplicationContext context, Throwable exception,
            Collection<SpringBootExceptionReporter> exceptionReporters, SpringApplicationRunListeners listeners) {
        try {
            try {
                /* 處理退出碼 */
                handleExitCode(context, exception);
                if (listeners != null) {
                    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);
    }

    /* 記錄異常錯誤資訊*/
    private void reportFailure(Collection<SpringBootExceptionReporter> exceptionReporters, Throwable failure) {
        try {
            for (SpringBootExceptionReporter reporter : exceptionReporters) {
                if (reporter.reportException(failure)) {
                    registerLoggedException(failure);
                    return;
                }
            }
        } catch (Throwable ex) {
            // Continue with normal handling of the original failure
        }
        if (logger.isErrorEnabled()) {
            logger.error("Application run failed", failure);
            registerLoggedException(failure);
        }
    }
    protected void registerLoggedException(Throwable exception) {
        SpringBootExceptionHandler handler = getSpringBootExceptionHandler();
        if (handler != null) {
            handler.registerLoggedException(exception);
        }
    }
    /*  根據應用退出碼處理  */
    private void handleExitCode(ConfigurableApplicationContext context, Throwable exception) {
        int exitCode = getExitCodeFromException(context, exception);
        if (exitCode != 0) {
            if (context != null) {
                context.publishEvent(new ExitCodeEvent(context, exitCode));
            }
            SpringBootExceptionHandler handler = getSpringBootExceptionHandler();
            if (handler != null) {
                handler.registerExitCode(exitCode);
            }
        }
    }