SpringApplication.run()创建初始化流程

1、分析


之前从 @SpringBootApplication 来入手我们可以看到 SpringBoot 的自动配置原理,那我们的整个 SpringBoot 应用到底是怎么启动起来的呢?它启动的过程中又干了那些事情呢?这就要从这一句神秘的代码开始探索了

SpringApplication.run(BootTestApplication.class, args);

大家要额外注意,因为 SpringBoot 在启动整个应用的过程中,有着非常多的时机,在这些时机中我们又可能需要自定义干一些活,我们需要一些回调机制来完成这个事情,所以 SpringBoot 底层对这一方面也做支持,所以我们在研究整个SpringBoot 的启动过程的时候,要额外注意一些叫 `xxxListener`、`xxxInitializer` 之类的,它们都是在合适的时机用来进行回调

先来到我们的 main(),我们的完整代码是

SpringApplication.run(BootTestApplication.class, args);

一传入我们的主配置类,就开始运行我们的 Spring 应用,怎么运行的呢?跟如就可看到
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
   return run(new Class<?>[] { primarySource }, args);
}

可以看到它首先是 new 了一个字节码数组把我们的主程序类传来,然后调用重载的 run(),我们就直接跟入它的 run()

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

这个 run()呢,首先第一步它会先创建 Spring 应用,即实例化出 SpringApplication 对象,然后第二步再把我们的应用跑起来,所以我们整个的启动过程分为两大步来研究


2、创建SpringApplication


首先我们来分析创建 SpringApplication 干了什么,我们继续跟入有参构造器

public SpringApplication(Class<?>... primarySources) {
   this(null, primarySources);
}

然后它调用 this 有参构造器,传入两个参数(null与我们的主配置类的字节码),我们继续跟入

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();
   this.bootstrappers = new ArrayList<>(getSpringFactoriesInstances(Bootstrapper.class));
   setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
   setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
   this.mainApplicationClass = deduceMainApplicationClass();
}

这个有参构造器里面我们可以看到它初始化了很多东西,下面一步步的来分析

第一步:可以看到它保存了第一个属性,this.resourceLoader 即资源加载器,为空

第二步:来断言我们必须要有主配置类,即 SpringApplication.run 中传入的第一个参数不能为空

第三步:将我们的主配置类信息用一个 LinkedHashSet 来保存起来

第四步:来判定当前应用的类型,即调用 deduceFromClasspath() 来进行判断

static WebApplicationType deduceFromClasspath() {
    //用它的工具类来判断我们当前的应用有没有导入响应式编程的请求处理器
   if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
         && !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
       //若有,就会返回他是一个响应式编程的
      return WebApplicationType.REACTIVE;
   }
   for (String className : SERVLET_INDICATOR_CLASSES) {
      if (!ClassUtils.isPresent(className, null)) {
         return WebApplicationType.NONE;
      }
   }
    //若不是,就会返回当前应用类型是一个原生的Servlet编程
   return WebApplicationType.SERVLET;
}

第五步:初始化所有的 bootstrappers(初始启动引导器)(调用getSpringFactoriesInstances()得到,默认0个)

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 = getClassLoader();
    //调用经典的SpringFactoriesLoaderd的loadFactoryNames()来获取所有类路径下META-INF下所有spring.factory配置文件		 中指定类型的组件,这里组件类型为BootStrap 
    Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    //创建实例
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
    //排序
    AnnotationAwareOrderComparator.sort(instances);
    //返回
    return instances;
}

第六步:初始化所有的 Initialiers(初始化器),获取的方式与第五步中的过程一样,加载所有类路径下 META-INF下所有 spring.factory 配置文件中类型为 ApplicationContextInitializer类型的组件(默认能找到7个)


第七步:初始化所有的 Listeners(应用监听器),用来监听当前应用的状态的,方式与第五、六步中一样,组件的类型为 ApplicationListener (默认能找到9个)

image-20210116150651178

第八步:通过找到堆栈,挨个遍历判断那个是有 main() 的,有 main()的类就是主程序类

​ 注:StackTrace记录函数调用堆栈

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;
}

image-20210116151215952

这八步就是我们的 SpringApplication 应用创建做的一堆事情,总结一下应用创建的过程就是把一些关键的组件通过读取配置文件等方式加载进来,先提前来保存到 ``SpringApplication` 对象里面,为我们后来的第二步:运行做准备!



3、运行SpringApplication


run()的源代码与文档注释如下:

解释:运行 Spring 应用,创建并且刷新一个新的 IOC 容器并返回

Run the Spring application, creating and refreshing a new ApplicationContext.
Params: args – the application arguments (usually passed from a Java main method)
Returns: a running ApplicationContext

public ConfigurableApplicationContext run(String... args) {
   StopWatch stopWatch = new StopWatch();
   stopWatch.start();
   DefaultBootstrapContext bootstrapContext = createBootstrapContext();
   ConfigurableApplicationContext context = null;
   configureHeadlessProperty();
   SpringApplicationRunListeners listeners = getRunListeners(args);
   listeners.starting(bootstrapContext, this.mainApplicationClass);
   try {
      ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
      ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
      configureIgnoreBeanInfo(environment);
      Banner printedBanner = printBanner(environment);
      context = createApplicationContext();
      context.setApplicationStartup(this.applicationStartup);
      prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
      refreshContext(context);
      afterRefresh(context, applicationArguments);
      stopWatch.stop();
      if (this.logStartupInfo) {
         new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
      }
      listeners.started(context);
      callRunners(context, applicationArguments);
   }
   catch (Throwable ex) {
      handleRunFailure(context, ex, listeners);
      throw new IllegalStateException(ex);
   }

   try {
      listeners.running(context);
   }
   catch (Throwable ex) {
      handleRunFailure(context, ex, null);
      throw new IllegalStateException(ex);
   }
   return context;
}

注意,run() 的形参传入了 args,而这个 args 说的其实就我们以前运行 Jar 包的时候,可能通过命令行的方式会传入一些参数,例如我们可以使用命令行参数的形式,启动项目的时候来指定配置文件新位置 (--spring.config.location),这就是这些传入的 args,我们未来在对命令行参数的解析也会得到的


2.1、StopWatch stopWatch = new StopWatch()

第一步上来就先 new 了一个 StopWatch,它是用来监控我们整个应用的启动与停止的



2.2、stopWatch.start()

第二步调用了 StopWatch 的 start(),我们跟入查看

    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.startTimeNanos = System.nanoTime();
        }
    }

发现功能很简单,就是给 StopWatch 里面保存一些信息。比如任务的名字与当前的系统时间,记录下来我们的应用是那一时刻启动的,以便最后可以算出具体的应用启动时长



2.3、DefaultBootstrapContext bootstrapContext = createBootstrapContext()

创建引导上下文(Context环境)

private DefaultBootstrapContext createBootstrapContext() {
   DefaultBootstrapContext bootstrapContext = new DefaultBootstrapContext();
   this.bootstrappers.forEach((initializer) -> initializer.intitialize(bootstrapContext));
   return bootstrapContext;
}

我们注意到在此过程中不仅返回了一个默认的 DefaultBootstrapContext

而且还将所有之前保存起来的 bootstrappers 进行了循环遍历调用了一遍 intitialize(),来完成对引导启动器上下文环境设置,默认为0个,如果你想写可以自行扩展



2.4、ConfigurableApplicationContext context = null

声明一个IOC容器为null,没啥好说的



2.5、configureHeadlessProperty()

让当前应用进入 headless模式(孤立无援模式)

private boolean headless = true;

private static final String SYSTEM_PROPERTY_JAVA_AWT_HEADLESS = "java.awt.headless";

private void configureHeadlessProperty() {
   System.setProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS,
         System.getProperty(SYSTEM_PROPERTY_JAVA_AWT_HEADLESS, Boolean.toString(this.headless)));
}

补充:

  • 什么是 java.awt.headless?
    Headless模式是系统的一种配置模式。在该模式下,系统缺少了显示设备、键盘或鼠标。

  • 何时使用和headless mode?
    Headless模式虽然不是我们愿意见到的,但事实上我们却常常需要在该模式下工作,尤其是 服务器端 程序开发者。因为服务器(如提供Web服务的 主机 )往往可能缺少前述设备,但又需要使用他们提供的功能,生成相应的数据,以提供给客户端(如浏览器所在的配有相关的 显示设备 、键盘和 鼠标 的主机)

  • 如何使用和Headless mode?
    一般是在程序开始激活headless模式,告诉程序,现在你要工作在Headless mode下,就不要指望硬件帮忙了,你得自力更生,依靠系统的计算能力模拟出这些特性来

  • 如果名字为java.awt.headless的系统属性被设置true,那么headless工具包就会被使用。应用程序可以执行如下操作:

    • 创建轻量级组件
    • 收集关于可用的字体、字体指标和字体设置的信息
    • 设置颜色来渲染准备图片
    • 创造和获取图像,为渲染准备图片
    • 使用java.awt.PrintJob,java.awt.print.*,和javax.print.*类里的打印

2.6、SpringApplicationRunListeners listeners = getRunListeners(args)

获取所有的运行监听器 SpringApplicationRunListener

private SpringApplicationRunListeners getRunListeners(String[] args) {
   Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
   return new SpringApplicationRunListeners(logger,
         getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args),
         this.applicationStartup);
}

老样子,调用 getSpringFactoriesInstances() 加载 spring.factory 中所有配置类型为SpringApplicationRunListener 类型的组件,默认为1个,类型为 EventPublishingRunListener

image-20210116191107027

注意其中的各个方法,就是用来在在启动过程中的各个时机进行回调通知的方法

  • starting

    在一切准备就绪,准备启动时

  • environmentPrepared

    在prepareEnvironment方法内部调用

  • contextPrepared

    在prepareContext的开始阶段被调用

  • contextLoaded

    在prepareContext的最后一步被调用

  • started

    在所有执行完成,ApplicationRunner和CommandLineRunner回调之前

  • running

    在run方法最后单独使用try catch执行,只要上面没有异常,项目已经启动完成。那么running回调异常也不能影 响正常流程

  • failed

    在handleRunFailure异常处理中被调用


2.7、listeners.starting(bootstrapContext, this.mainApplicationClass)

void starting(ConfigurableBootstrapContext bootstrapContext, Class<?> mainApplicationClass) {
   doWithListeners("spring.boot.application.starting", (listener) -> listener.starting(bootstrapContext),
         (step) -> {
            if (mainApplicationClass != null) {
               step.tag("mainApplicationClass", mainApplicationClass.getName());
            }
         });
}

private void doWithListeners(String stepName, Consumer<SpringApplicationRunListener> listenerAction,
      Consumer<StartupStep> stepAction) {
   StartupStep step = this.applicationStartup.start(stepName);
   this.listeners.forEach(listenerAction);
   if (stepAction != null) {
      stepAction.accept(step);
   }
   step.end();
}

它在这里遍历所有的 listeners,将然后调用它的 starting(),相当于告诉你,哎我当前的项目正在启动,通知所有感兴趣系统正在启动过程的人,项目正在 starting!方便了所有的 listener 进行事件感知,这也是我们 SpringApplicationRunListener 的第一个事件



2.8、ApplicationArguments applicationArguments = new DefaultApplicationArguments(args)

try的第一步用来对命令行的参数信息进行保存(封装到 ApplicationArguments 类中)



2.9、ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments)

这一步用来准备环境信息

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
      DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
   // Create and configure the environment
    //若当前上下文有环境信息就获取,没有就创建
   ConfigurableEnvironment environment = getOrCreateEnvironment();
   configureEnvironment(environment, applicationArguments.getSourceArgs());
   ConfigurationPropertySources.attach(environment);
   listeners.environmentPrepared(bootstrapContext, environment);
   DefaultPropertiesPropertySource.moveToEnd(environment);
   configureAdditionalProfiles(environment);
   bindToSpringApplication(environment);
   if (!this.isCustomEnvironment) {
      environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
            deduceEnvironmentClass());
   }
   ConfigurationPropertySources.attach(environment);
   return environment;
}

配置Environment,主要有三点,一是ConversionService,二是数据源,包括命令行参数,三是Profiles

2.9.1、ConfigurableEnvironment environment = getOrCreateEnvironment()

private ConfigurableEnvironment getOrCreateEnvironment() {
   if (this.environment != null) {
      return this.environment;
   }
   switch (this.webApplicationType) {
   case SERVLET:
      return new StandardServletEnvironment();
   case REACTIVE:
      return new StandardReactiveWebEnvironment();
   default:
      return new StandardEnvironment();
   }
}

这一步用来返回或创建基础环境信息对象。若当前的环境不为空,则返回当前的环境,否则根据当前的应用类型来返回不同的应用环境,比如我们当前的环境类型为SERVLET,就会返回 StandardServletEnvironment 类型环境


2.9.2、configureEnvironment(environment, applicationArguments.getSourceArgs())

这一步用来配置环境信息,方法传入了上一步默认创建的环境与命令行的参数信息

protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
   if (this.addConversionService) {
      ConversionService conversionService = ApplicationConversionService.getSharedInstance();
      environment.setConversionService((ConfigurableConversionService) conversionService);
   }
   configurePropertySources(environment, args);
   configureProfiles(environment, args);
}

首先给我们的环境信息中填了一些 ConversionService 这些 ConversionService 又是什么呢?这其实就是我们的类型转换器,我们要 A 类型转 B 类型,B 类型转 A 类型啦,或者要读取配置文件,都会用到它

接下来调用 configurePropertySources(environment, args) 读取所有的配置源配置属性信息

image-20210116194714203

向环境对象中填充了 Servlet 中的初始化参数,以及本机系统的环境变量(System.getenv)与 JVM 的环境变量(System.getProperties)

最后的 configureProfiles(environment, args) 为空方法,采用默认的Profiles

2.9.3、ConfigurationPropertySources.attach(environment)

这一步将配置好的环境信息绑定,就是一个进行保存的工作



2.9.4、listeners.environmentPrepared(bootstrapContext, environment)

void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
   doWithListeners("spring.boot.application.environment-prepared",
         (listener) -> listener.environmentPrepared(bootstrapContext, environment));
}

将所有的监听器调用 environmentPrepare() 方法,这也是我们 SpringApplicationRunListener 的第二个事件,通知所有的监听器当前应用的环境已经准备完成!

SpringBoot默认配置文件 application.yml 的加载也在这一步完成,是通过 Spring 的默认实现:EventPublishingApplicationRunListener 来做的工作

@Override
public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext,
      ConfigurableEnvironment environment) {
   this.initialMulticaster.multicastEvent(
         new ApplicationEnvironmentPreparedEvent(bootstrapContext, this.application, this.args, environment));
}

environmentPrepared 触发了 ApplicationEnvironmentPreparedEvent 事件,这个事件是在 spring.factory 里面配置的 ConfigDataEnvironmentPostProcessor 处理的,最终通过其中的postProcessEnvironment() 中调用的getConfigDataEnvironment(environment, resourceLoader, additionalProfiles).processAndApply() 来进行配置文件的加载

2.9.5、DefaultPropertiesPropertySource.moveToEnd(environment)

public static void moveToEnd(MutablePropertySources propertySources) {
   PropertySource<?> propertySource = propertySources.remove(NAME);
   if (propertySource != null) {
      propertySources.addLast(propertySource);
   }
}

跟入,这一步是将 environment 对象中 propertySources 属性值为 NAME=defaultProperties 的移动到 propertySources 的最后



2.9.6、configureAdditionalProfiles(environment)

配置多环境信息

private void configureAdditionalProfiles(ConfigurableEnvironment environment) {
   if (!CollectionUtils.isEmpty(this.additionalProfiles)) {
      Set<String> profiles = new LinkedHashSet<>(Arrays.asList(environment.getActiveProfiles()));
      if (!profiles.containsAll(this.additionalProfiles)) {
         profiles.addAll(this.additionalProfiles);
         environment.setActiveProfiles(StringUtils.toStringArray(profiles));
      }
   }
}

若项目启动时在配置文件中指定了多环境,则在这里激活多环境配置,此时默认环境与激活的环境同时生效



2.9.7、if (!this.isCustomEnvironment)

StandardEnvironment convertEnvironmentIfNecessary(ConfigurableEnvironment environment,
      Class<? extends StandardEnvironment> type) {
   if (type.equals(environment.getClass())) {
      return (StandardEnvironment) environment;
   }
   return convertEnvironment(environment, type);
}

将给定的环境转换为给定的 StandardEnvironment 类型

如果环境已经是同一类型,则不执行转换,并且返回时保持不变

2.9.8、bindToSpringApplication(environment)

把配置好的环境绑定到 SpringApplication,这一步也是信息的保存


2.10、configureIgnoreBeanInfo(environment)

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());
   }
}

配置一些需要忽略的 bean 信息,若System中的spring.beaninfo.ignore属性为空,就把当前环境中的属性覆盖上去

这个系统属性暂时不知道 Spring 拿来有什么作用



2.11、Banner printedBanner = printBanner(environment)

话不多说,直接上图image-20210116214719306



2.12、context = createApplicationContext()

这一步至关重要,创建 IOC 容器

protected ConfigurableApplicationContext createApplicationContext() {
   return this.applicationContextFactory.create(this.webApplicationType);
}

ApplicationContextFactory DEFAULT = (webApplicationType) -> {
    try {
        switch (webApplicationType) {
            case SERVLET:
                return new AnnotationConfigServletWebServerApplicationContext();
            case REACTIVE:
                return new AnnotationConfigReactiveWebServerApplicationContext();
            default:
                return new AnnotationConfigApplicationContext();
        }
    }
    catch (Exception ex) {
        throw new IllegalStateException("Unable create a default ApplicationContext instance, "
                                        + "you may need a custom ApplicationContextFactory", ex);
    }
};

怎么创建的呢?非常简单,根据我们当前应用的类型来返回指定的 IOC 容器,比比如我们现在应用的类型为 SERVLET,返回的就是 AnnotationConfigServletWebServerApplicationContext 类型的容器



2.13、context.setApplicationStartup(this.applicationStartup)

IOC容器再来记录我们当前应用的 Startup 事件,将 StartUp 这个信息保存起来



2.14、prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner)

准备已经实例化的 IOC 容器所需要的具体信息

private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
      ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
      ApplicationArguments applicationArguments, Banner printedBanner) {
    //先给我们的IOC容器将上几步准备好的环境设置上
   context.setEnvironment(environment);
    //后置处理 IOC 容器 (Spring Boot 的)
   postProcessApplicationContext(context);
    //应用初始化器,遍历所有的ApplicationContextInitializer,调用其initialize()对容器进行初始化扩展
    //各种Initializer有着不同的功能,根据其自己的初始化逻辑对容器做一些扩展初始化操作
   applyInitializers(context);
    //调用所有SpringApplicationRunListener的contextPrepared(),告知IOC的上下文已经准备好了!
   listeners.contextPrepared(context);
    //引导上下文环境关闭
   bootstrapContext.close(context);
   if (this.logStartupInfo) {
      logStartupInfo(context.getParent() == null);
      logStartupProfileInfo(context);
   }
   // 拿到我们的Bean工厂
   ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
    //给容器中注册一些特别的原始的单实例Bean:例如命令行参数、横幅
   beanFactory.registerSingleton("springApplicationArguments", applicationArguments);
   if (printedBanner != null) {
      beanFactory.registerSingleton("springBootBanner", printedBanner);
   }
   if (beanFactory instanceof DefaultListableBeanFactory) {
      ((DefaultListableBeanFactory) beanFactory)
            .setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
   }
   if (this.lazyInitialization) {
      context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());
   }
   // Load the sources
   Set<Object> sources = getAllSources();
   Assert.notEmpty(sources, "Sources must not be empty");
   load(context, sources.toArray(new Object[0]));
    //通知所有的监听器,上下文环境加载完成,调用所有监听器的contextLoaded()
   listeners.contextLoaded(context);
}

image-20210116220626111

至此,我们的整个 IOC 容器的准备工作就已经做完了!



2.15、refreshContext(context)

接下来就是刷新 IOC 容器,会调用 Spring 经典的 refresh() 来进行刷新,会进行所有的非懒加载的单实例 Bean 的注册,即创建容器中的所有组件



2.16、afterRefresh(context, applicationArguments)

IOC 容器刷新完成后要做的工作在这一步来完成

protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) {
}

跟进去一看发现什么工作也不做,即 Spring 也没想好应该干点啥


2.17、stopWatch.stop()

首位呼应,记录启动完成时间,以便计算出启动时长



2.18、listeners.started(context)

通知所有的监听器,我们又有一个活干好了:项目已经started!



2.19、callRunners(context, applicationArguments)

调用所有的 Runners ,这些 Runners 又是什么呢?我们跟入看一下

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);
    }
}

它从容器中按照类型获取 ApplicationRunnerCommandLineRunner 这两种组件,并把它们放入一个集合中,并且调用 AnnotationAwareOrderComparator.sort(runners) 对集合进行排序,相当于按照 @Order 注解进行排序,顺序靠前的被排在前面

然后遍历所有的 Runner ,调用 run()

2.20、handleRunFailure(context, ex, listeners)

如果以上有异常,调用所有监听器的 failed(),来监控当前应用启动失败的事件

private void handleRunFailure(ConfigurableApplicationContext context, Throwable exception,
      SpringApplicationRunListeners listeners) {
   try {
      try {
         handleExitCode(context, exception);
         if (listeners != null) {
            listeners.failed(context, exception);
         }
      }
      finally {
         reportFailure(getExceptionReporters(context), exception);
         if (context != null) {
            context.close();
         }
      }
   }
   catch (Exception ex) {
      logger.warn("Unable to close ApplicationContext", ex);
   }
   ReflectionUtils.rethrowRuntimeException(exception);
}



2.21、listeners.running(context)

什么问题也没有,最后调用所有监听器的 Running(),通知所有的监听器项目已经跑起来了

2.22、handleRunFailure(context, ex, listeners)

如果 2.21中的 Running() 有问题,继续调用所有监听器的 failed()


至此整个的SpringBoot应用的启动全部结束,返回的是一个IOC容器 ~

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大恐龙的小弟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值