1.引言
上篇文章我们知道了,springboot应用启动流程分为两步:
- 通过@SpringBootApplication注解完成组件扫描,以及自动装配
- 通过SpringApplication.run方法完成引导应用程序启动
那么本篇文章,我们一起来探讨run方法是如何完成应用程序的引导启动的?引导启动的步骤稍微有点多和繁琐,但如果你熟悉spring应用启动流程的话,还是很容易就理解了,springboot应用的启动流程,是在spring应用启动流程的基础上,做了一些扩展,仅此而已。
具体来看看。
2.启动流程分析
2.1.应用入口
public static void main(String[] args) {
SpringApplication.run(EduCommonApplication.class, args);
}
2.2.进入run方法
/**
* Static helper that can be used to run a {@link SpringApplication} from the
* specified source using default settings.
* @param primarySource the primary source to load
* @param args the application arguments (usually passed from a Java main method)
* @return the running {@link ApplicationContext}
*/
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[] { primarySource }, args);
}
2.3.进入重载run方法:
/**
* 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) {
// 创建SpringApplication实例,并调用run方法
return new SpringApplication(primarySources).run(args);
}
重点关注的run方法:
- 方法注释写的很清楚,创建和刷新ApplicationContext对象,我们知道ApplicationContext即是spring应用的核心
- 返回ConfigurableApplicationContext实例,即IoC容器
- 方法内容有点多,我们通过标记序号,然后依据序号在后面详细分析每个步骤
/**
* Run the Spring application, creating and refreshing a new
* {@link ApplicationContext}.
* @param args the application arguments (usually passed from a Java main method)
* @return a running {@link ApplicationContext}
*/
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
configureHeadlessProperty();
// 第一步
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
// 第二步
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
// 第三步
Banner printedBanner = printBanner(environment);
// 第四步
context = createApplicationContext();
// 第五步
prepareContext(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;
}
第一步: 首先是关于事件监听的处理,springboot在启动过程中,通过监听器机制给应用程序预留了扩展能力。这里的
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
获取到的是事件发布者。在监听器机制中,共有三个角色:
- 事件
- 事件监听器
- 事件发布者
那么SpringApplicationRunListener扮演的是事件发布者,它会在springboot应用启动的不同时间点,发布不同类型的事件(ApplicationEvent)。然后相应的事件监听器便会接受到相应的事件,并进行处理。
SpringApplicationRunListener源码:每个方法相应在不同的时间点进行事件发布
public interface SpringApplicationRunListener {
// 调用run方法后,最先调用的方法,用于完成早期的初始化工作
default void starting() {}
// 运行环境:environment准备好,且在ApplicationContext创建前调用
default void environmentPrepared(ConfigurableEnvironment environment) {}
// 创建完成ApplicationContext后调用
default void contextPrepared(ConfigurableApplicationContext context) {}
// ApplicationContext加载完成,在refresh前调用
default void contextLoaded(ConfigurableApplicationContext context) {}
// ApplicationContext完成refresh,且应用启动后调用
default void started(ConfigurableApplicationContext context) {}
// SpringApplication的run调用完成,且ApplicationContext完成refresh后调用
default void running(ConfigurableApplicationContext context) {}
// 启动过程中,发生意外的时候调用
default void failed(ConfigurableApplicationContext context, Throwable exception) {}
}
SpringApplicationRunListener接口,唯一的实现类:
public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
@Override
public void starting() {
this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
}
@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
this.initialMulticaster
.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
}
@Override
public void contextPrepared(ConfigurableApplicationContext context) {
this.initialMulticaster
.multicastEvent(new ApplicationContextInitializedEvent(this.application, this.args, context));
}
.............................................................
}
源码有点多,就不贴完整的了。从starting,environmentPrepared,contextPrepared分别发布了不同的事件:
- ApplicationStartingEvent
- ApplicationEnvironmentPreparedEvent
- ApplicationContextInitializedEvent
到这里,我们基本上搞清楚了第一步做的事情。
第二步:
第二步主要是准备运行环境:Environment,包含两个方面的内容
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
- 分环境配置特性:profile
- 属性配置:properties
有一定开发经验的同学,对这两块内容都不会陌生。profile特性用于指定不同的应用运行环境,比如说:开发环境,测试环境,预发布环境,生产环境,可以使用不同的配置文件;properties属性来源于配置文件,环境变量,命令行参数等。
当Environment准备好之后,应用生命周期的任何时候,都可以从Environment中获取资源配置。
第三步:
第三步不需要过多的介绍,我截个图你就知道是什么了
Banner printedBanner = printBanner(environment);
第四步:
根据是否是web应用,来创建对应的ApplicationContext
context = createApplicationContext();
第五步:
初始化ApplicationContext,主要完成以下工作:
- 将准备好的Environment设置给ApplicationContext
- 遍历调用ApplicationContextInitializer的initialize()方法,来对已经创建好的ApplicationContext进行进一步的处理
- 调用SpringApplicationRunListener的contextPrepared()方法,通知所有的监听者:ApplicationContext已经准备完毕
- 将所有的bean加载到容器中
- 调用SpringApplicationRunListener的contextLoaded()方法,通知所有的监听者:ApplicationContext已经装载完毕
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
第六步:
调用ApplicationContext的refresh方法,完成IoC容器可用的最后一道工序 。
refreshContext(context);
refresh的意思是刷新,简单解释一下刷新到底做了什么?为什么刚创建,初始化的ApplicationContext需要刷新呢?新鲜的还要刷新,显得有那么点突然和怪异!
其实它要做什么事情呢?就是获取BeanFactoryPostProcessor来对容器做一些额外的操作。BeanFactoryPostProcessor允许我们在容器实例化相应对象之前,对注册到容器的BeanDefinition所保存的信息做一些额外的操作。
第七步:
查找当前context中是否注册有CommandLineRunner和ApplicationRunner,如果有则遍历执行它们 。大多数应用,我们不太需要关注这一步。
afterRefresh(context, applicationArguments);
到此,基本上就是springboot应用整个的启动引导流程了。
最后总结一下需要关注的几个步骤:
- 第一步:与事件监听机制相关,需要重点关注
- 第二步:与应用的运行环境相关,需要重点关注
- 第四步:创建应用上下文ApplicationContext,需要重点关注
- 第五步:初始化准备ApplicationContext容器,需要重点关注
- 第六步:完成IoC容器可用的最后一道工序,需要重点关注