前面我们对SpringApplication对象的构造流程进行了浅析,SpringApplication对象构造完成后会调用其run方法进行Spring Boot的启动和运行,本文开始重点分析Spring Boot是如何通过run方法完成整个项目的启动和运行的。
目录
一、获取SpringApplicationRunListener监听器、启动监听器
二、创建ApplicationArguments对象、初始化ConfigurableEnvironment
七、调用ApplicationRunner和CommandLineRunner
run方法总览
首先回顾一下本文要剖析的run方法是在哪里出现的(并非启动类中的run方法):在Spring Boot构造流程浅析中我们跟进启动类中的run方法后发现SpringApplication.run(xxx.class)主要进行SpringApplication类的实例化操作,然后这个实例化对象再去调用了另外一个更牛逼的run方法来完成整个项目的初始化和启动,而这个更牛逼的run方法就是本文的主角。
public static ConfigurableApplicationContext run(Class<?>[] primarySources,
String[] args) {
return new SpringApplication(primarySources).run(args);
}
进入这个run方法,源码及注释如下。
public ConfigurableApplicationContext run(String... args) {
//用于统计run方法启动时长
StopWatch stopWatch = new StopWatch();
//启动统计
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
//获取SpringApplicationRunListener监听器
SpringApplicationRunListeners listeners = getRunListeners(args);
//启动监听器
listeners.starting();
try {
//创建ApplicationArguments对象
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
//初始化ConfigurableEnvironment
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
//忽略信息配置
configureIgnoreBeanInfo(environment);
//打印Banner
Banner printedBanner = printBanner(environment);
//创建容器
context = createApplicationContext();
//异常报告器
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
//准备容器
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);
//调用ApplicationRunner和CommandLineRunner的运行方法
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
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;
}
忽略调一些非核心操作现在我们来总结一下run方法的核心操作流程:
- 获取SpringApplicationRunListener监听器、启动监听器
- 创建ApplicationArguments对象、初始化ConfigurableEnvironment
- 忽略信息配置
- 打印Banner
- 创建容器
- 准备容器
- 刷新容器
- 调用ApplicationRunner和CommandLineRunner
下面将逐步浅析上述流程。
一、获取SpringApplicationRunListener监听器、启动监听器
现在开始对run方法的核心流程一步一步的分析,首先是通过getRunListeners方法获取到SpringApplicationRunListeners,可以把SpringApplicationRunListeners理解为一个容器,它里面存放了很多SpringApplicationRunListener相关的监听器,下面是该方法源码:
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
SpringApplicationRunListener.class, types, this, args));
}
发现getRunListeners也只是调用了SpringApplicationRunListeners的构造方法而已,注意看该构造方法的第二个参数是去调用了getSpringFactoriesInstances方法,这个方法的主要作用是去获取META-INF/spring.factories中对应的监听器配置,并进行实例化操作,源码如下:
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
//加载spring.factories文件中监听器的配置
Set<String> names = new LinkedHashSet<>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
//实例化刚才获取到的监听器
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
//排序
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
可以看到这里依然是通过SpringFactoriesLoader.loadFactoryNames来加载spring.factories中的监听器配置信息,该方法在之前Spring Boot自动配置原理浅析和Spring Boot构造流程浅析已经多次提到,这里就不再重复说明了。最后是通过createSpringFactoriesInstances方法来实例化刚获取到的监听器,再将这些实例经过排序后返回,返回的结果是一个SpringApplicationRunListener集合。
现在我们进入SpringApplicationRunListeners的构造方法来看一下:
SpringApplicationRunListeners(Log log,
Collection<? extends SpringApplicationRunListener> listeners) {
this.log = log;
this.listeners = new ArrayList<>(listeners);
}
发现原来刚才获取到的集合被传入SpringApplicationRunListeners的构造方法后,只是简单的赋值给了listeners成员变量而已。在获取到监听器后,将会调用listeners.starting()来启动监听器。
在构造方法下面还能看到很多关于listeners成员变量的各种遍历操作方法: starting、environmentPrepared、contextPrepared、contextLoaded、started、running、failed。在run方法执行期间将会调用这些方法,通过下面的图来了解下这些方法具体将会在什么时候被调用。
可以结合图片和后面的源码内容做对比,加深理解。
二、创建ApplicationArguments对象、初始化ConfigurableEnvironment
在初始化ConfigurableEnvironment之前还需要先初始化ApplicationArguments,我们只需要知道ApplicationArguments的作用是提供访问运行SpringApplication的参数几即可。接着便是初始化ConfigurableEnvironment。ConfigurableEnvironment的作用是对“环境”服务的,是提供当前运行环境的接口。
run方法中初始化ConfigurableEnvironment的相关代码如下,可以看到传入的参数是之前获取到的监听器以及ApplicationArguments。
//初始化ConfigurableEnvironment
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
进入prepareEnvironment方法,源码及注释如下:
private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
//获取或创建环境
ConfigurableEnvironment environment = getOrCreateEnvironment();
//配制环境
configureEnvironment(environment, applicationArguments.getSourceArgs());
//将ConfigurationPropertySources附加到指定环境的第一位
ConfigurationPropertySources.attach(environment);
listeners.environmentPrepared(environment);
//将环境绑定到SpringApplication
bindToSpringApplication(environment);
//判断是否是定制的环境,如果不是,则将将环境转换为StandardEnvironment
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader())
.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
}
//将ConfigurationPropertySources附加到指定环境的第一位
ConfigurationPropertySources.attach(environment);
return environment;
}
大致流程:首先通过getOrCreateEnvironment方法获取或创建环境;然后配置环境;接着将ConfigurationPropertySources附加到指定环境的第一位,动态跟踪环境的添加和删除;调用listeners的environmentPrepared方法通知环境准备(对比之前的图片理解);将环境绑定到SpringApplication;判断是否是定制的环境,如果不是,则将将环境转换为标准环境;最后一步也是将ConfigurationPropertySources附加到指定环境的第一位,动态跟踪环境的添加和删除。
三、忽略信息配置
在初始化ConfigurableEnvironment以后,还需要通过configureIgnoreBeanInfo方法进行忽略信息配置,该方法源码如下:
private void configureIgnoreBeanInfo(ConfigurableEnvironment environment) {
//若系统中spring.beainfo.ignore的值为null
if (System.getProperty(
CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME) == null) {
//获取环境中spring.beainfo.ignore配置的值
Boolean ignore = environment.getProperty("spring.beaninfo.ignore",
Boolean.class, Boolean.TRUE);
//将系统中spring.beainfo.ignore的值设为环境中的值
System.setProperty(CachedIntrospectionResults.IGNORE_BEANINFO_PROPERTY_NAME,
ignore.toString());
}
}
可以明白这一过程大概就是:如果系统中“spring.beaninfo.ignore”的值为null,则把它设置为环境中“spring.beaninfo.ignore”配置的值,spring.beaninfo.ignore的作用是用来决定是否跳过BeanInfo类的扫描。
四、打印Banner
打印Banner其实没什么可说的,我们启动SpringBoot项目时能看到如下图的打印信息。这主要是通过printBanner方法来实现的,由于这个流程并不是很重要,我们甚至可以关闭Banner的打印,因此这个步骤就不再此过多说明。
五、创建容器
接着是创建容器ConfigurableAoolicationContext,即Spring应用上下文的创建,这个时候如果没有指定要创建的类,则会根据之前推断出来的类型进行上下文类的创建。
还记得在Spring Boot构造流程浅析中进行的web应用类型推断吗?回顾代码片段:
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
……
//推断Web应用类型
this.webApplicationType = WebApplicationType.deduceFromClasspath();
……
}
现在就要根据这个被推断出来的应用类型进行上下文类的创建,即容器的创建。来看一下创建容器的方法:createApplicationContext()
protected ConfigurableApplicationContext createApplicationContext() {
//首先获取容器的变量
Class<?> contextClass = this.applicationContextClass;
//如果容器的变量还为null,则根据web应用类型创建容器
if (contextClass == null) {
try {
switch (this.webApplicationType) {
case SERVLET:
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);
}
}
//最后通过BeanUtils进行实例化
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}
用一句话总结就是先获取容器的变量,如果此时容器的变量还为null,那么就根据web应用类型去创建容器,最终通过BeanUtils进行实例化后返回。
六、准备容器
在创建完容器之后,接下来就是通过prepareContext方法做准备容器的工作,即Spring应用上下文的准备。来看一下prepareContext方法的源码及注释:
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
//设置环境
context.setEnvironment(environment);
//应用上下文后置处理
postProcessApplicationContext(context);
//初始化context
applyInitializers(context);
//通知监听器context准备完成
listeners.contextPrepared(context);
//打印日志、启动Profile
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
//通过context拿到ConfigurableListableBeanFactory 并注册单例对象
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
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());
}
//获取全部配置源
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
//将sources中的bean加载到context中
load(context, sources.toArray(new Object[0]));
//通知监听器context加载完成
listeners.contextLoaded(context);
}
可以看到虽然这个方法整体定义为准备上下文的过程,但分析其方法内部逻辑又可以细分为两个部分:应用上下文的准备和加载。我们将准备容器的核心流程整理如下:
准备容器 | 应用上下文的准备 |
|
应用上下文的加载 |
|
七、刷新容器
容器准备工作做完之后接下来是刷新容器,即Spring应用上下文的刷新。
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();
}
Spring应用上下文的刷新是通过refreshContext方法来完成,追溯其源码可知核心方法是其内部的refresh方法,而该方法又调用了AbstractApplicationContext中的refresh方法,我们可以通过源码来了解一下这个refresh方法内部都做了些什么。
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
//准备刷新
prepareRefresh();
//通知子类刷新内部bean工厂
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
//为context准备bean工厂
prepareBeanFactory(beanFactory);
try {
// 允许context的子类对bean工厂进行后置处理
postProcessBeanFactory(beanFactory);
// 调用context中注册为bean的工厂处理器
invokeBeanFactoryPostProcessors(beanFactory);
// 注册bean处理器
registerBeanPostProcessors(beanFactory);
// 初始化context的信息源
initMessageSource();
// 初始化context的事件传播器
initApplicationEventMulticaster();
// 初始化其他子类特殊的bean
onRefresh();
// 注册事件监听器
registerListeners();
// 实例化所有非懒加载单例
finishBeanFactoryInitialization(beanFactory);
// 发布对应事件
finishRefresh();
}
七、调用ApplicationRunner和CommandLineRunner
最后,run方法会通过执行callRunners方法来调用ApplicationRunner和CommandLineRunner,目的通过他们来实现在容器启动时执行一些操作,如果有多个实现类,可以通过@Order注解或实现Order接口来控制执行顺序。来看callRunners方法源码:
private void callRunners(ApplicationContext context, ApplicationArguments args) {
//准备一个list集合
List<Object> runners = new ArrayList<>();
//从context中获取ApplicationRunner和CommandLineRunner的bean,放入集合
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
//排序
AnnotationAwareOrderComparator.sort(runners);
//遍历集合将ApplicationArguments 参数传入
for (Object runner : new LinkedHashSet<>(runners)) {
if (runner instanceof ApplicationRunner) {
callRunner((ApplicationRunner) runner, args);
}
if (runner instanceof CommandLineRunner) {
callRunner((CommandLineRunner) runner, args);
}
}
}
大致流程是通过context拿到ApplicationRunner和CommandLineRunner的bean,放入集合然后排序,然后遍历集合将ApplicationArguments 参数传入执行callRunner方法。
至此,Spring Boot运行流程执行结束。