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个)
第八步:通过找到堆栈,挨个遍历判断那个是有 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;
}
这八步就是我们的 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
注意其中的各个方法,就是用来在在启动过程中的各个时机进行回调通知的方法
-
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)
读取所有的配置源配置属性信息
向环境对象中填充了 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)
话不多说,直接上图
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);
}
至此,我们的整个 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);
}
}
它从容器中按照类型获取 ApplicationRunner
、CommandLineRunner
这两种组件,并把它们放入一个集合中,并且调用 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容器 ~