- 1. 前言
- 2. SpringApplication的初始化
- 3. SpringApplication的run方法
- 3.1. 1. Headless模式设置
- 3.2. 2. 加载SpringApplicationRunListeners监听器
- 3.3. 3. 封装ApplicationArguments对象
- 3.4. 4. 配置环境模块
- 3.5. 5. 根据环境信息配置要忽略的bean信息
- 3.6. 6. Banner配置SpringBoot彩蛋
- 3.7. 7. 创建ApplicationContext应用上下文
- 3.8. 8. 加载SpringBootExceptionReporter
- 3.9. 9. ApplicationContext基本属性配置
- 3.10. 10. 更新应用上下文
- 3.11. 11. afterRefresh()
- 3.12. 12. callRunner()
- 4. SpringBoot启动流程总结
- 5. 小结
- 6. 参考资料 & 鸣谢
前言
还是从SpringBoot的启动类说起,这篇文章主要分析启动类中的SpringApplication1
2
3
4
5
6
7
8@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
可以看出main函数中重要的就是SpringApplication.run()
,这可以分为两部分来探讨:
- SpringApplication的构造过程
- SpringApplication的run()方法
SpringApplication的初始化
首先进入SpringApplication的构造函数,先是单个参数的构造方法,后进入两个参数的构造方法,ResourceLoader是Spring的资源加载器,这里没有自定义的ResourceLoader传入,所以为NULL,而primarySources参数就是我们传入的Application.class启动类1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//1. 推断应用类型
this.webApplicationType = deduceWebApplicationType();
//2. initializer初始化模块,加载ApplicationContextInitializer
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
//3. 加载监听器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//4. 配置应用main方法所在的类
this.mainApplicationClass = deduceMainApplicationClass();
}
SpringApplication的初始化主要包括以下4个步骤:
- 推断应用类型
- 加载初始化构造器ApplicationContextInitializer
- 创建应用监听器
- 设置应用main()方法所在的类
1. 推断应用类型
this.webEnvironment=deduceWebApplicationType(); 判断应用的类型,是否是servlet应用还是reactive应用或者是none,webEnvironment中定义了这三种类型
2. 加载初始化构造器ApplicationContextInitializer
setInitializers((Collection)getSpringFactoriesInstances(ApplicationContextInitializer.class)): 通过SpringFactoriesLoader在应用的classpath中查找并加载所有可用的ApplicationContextInitializer
进入loadFactoryNames方法,然后进入loadSpringFactories方法,获取当前ClassLoader下的所有META-INF/spring.factories文件的配置信息
而后通过loadSpringFactories(classloader).getOrDefault(factoryClassName,Collections.emptyList()) 从所有META-INF/spring.factories文件的配置信息的map中获取指定的factory的值
默认情况下,从 spring.factories 文件找出的 key 为 ApplicationContextInitializer 的类有如上图中所示4种
对于 ApplicationContextInitializer,它是应用程序初始化器,做一些初始化工作1
2
3
4
5
6
7
8
9public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {
/**
* Initialize the given application context.
* @param applicationContext the application to configure
*/
void initialize(C applicationContext);
}
3. 创建应用监听器
setListeners()方法与setInitializers()方法类似,只不过它是使用SpringFactoriesLoader在应用的classpath的META-INT/spring.factories中查找并加载所有可用的ApplicationListener1
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
ApplicationListener,应用程序事件(ApplicationEvent)监听器:
1 | public interface ApplicationListener<E extends ApplicationEvent> extends EventListener { |
更详细的分析可以参阅我之前的文章: springboot系列文章之启动时初始化数据
4. 设置应用main()方法所在的类
在SpringApplication构造函数的最后一步,根据调用栈推断并设置main方法的定义类1
2
3
4
5
6
7
8
9
10
11
12
13
14private 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的run方法
SpringApplication实例初始化完成并且完成设置后,就可以开始run方法的逻辑了,对于这个run方法我将分为以下几点进行逐步剖析,而StopWatch是一个工具类,主要是方便记录程序运行时间,这里就不仔细介绍了。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62public ConfigurableApplicationContext run(String... args) {
//构造一个任务执行观察期
StopWatch stopWatch = new
StopWatch();
//开始执行,记录开始时间
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
//1
configureHeadlessProperty();
//2
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
//3
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
//4
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
//5
configureIgnoreBeanInfo(environment);
//6
Banner printedBanner = printBanner(environment);
//7
context = createApplicationContext();
//8
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
//9
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
//10
refreshContext(context);
//2.0版本中是空实现
//11
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
//12
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;
}
SpringApplication的run方法主要分为以下几步:
- Headless模式设置
- 加载SpringApplicationRunListeners监听器
- 封装ApplicationArguments对象
- 配置环境模块
- 根据环境信息配置要忽略的bean信息
- Banner配置SpringBoot彩蛋
- 创建ApplicationContext应用上下文
- 加载SpringBootExceptionReporter
- ApplicationContext基本属性配置
- 更新应用上下文
- 查找是否注册有CommandLineRunner/ApplicationRunner
1. Headless模式设置
configureHeadlessProperty()
设置 headless 模式,即设置系统属性java.awt.headless,它是J2SE的一种模式,用于在缺少显示屏,键盘,或者鼠标时的系统配置,该属性会被设置为true,更多的信息可以参考这里1
2
3
4
5
6private 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)));
}
2. 加载SpringApplicationRunListeners监听器
1 | SpringApplicationRunListeners listeners = getRunListeners(args); |
getRunListeners(args)
也是通过 SpringFactoriesLoader
从META-INF/spring.factories
查找到并加载的SpringApplicationRunListener
。该类实际上是监听SpringApplication的run方法的执行1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
SpringApplicationRunListener.class, types, this, args));
}
.....
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
//通过SpringFactoriesLoader可以查找到并加载的SpringApplicationRunListner
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
这里的SpringApplicationRunListener监听器与SpringApplication时加载的ApplicationListener监听器不同,SpringApplicationRunListener是SpringBoot新增的类,SpringApplicationRunListener目前只有一个实现类EventPublishingRunListener。虽然说是新增的, 但是它们之间是有联系的,它们之间的的关系是通过ApplicationEventMulticaster广播出去的SpringApplicationEvent所联系起来的
更详细的分析请参阅 :SpringBoot源码分析之SpringBoot的启动过程
3. 封装ApplicationArguments对象
将args参数封装成 ApplicationArguments
对象1
2
3
4
5public DefaultApplicationArguments(String[] args) {
Assert.notNull(args, "Args must not be null");
this.source = new Source(args);
this.args = args;
}
官网对 ApplicationArguments
的解释如下
4. 配置环境模块
根据listeners
和applicationArguments
创建并配置当前SpringBoot应用将要使用的Enviroment1
2
3
4
5
6
7
8
9
10
11
12
13
14
15private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
listeners.environmentPrepared(environment);
bindToSpringApplication(environment);
if (this.webApplicationType == WebApplicationType.NONE) {
environment = new EnvironmentConverter(getClassLoader())
.convertToStandardEnvironmentIfNecessary(environment);
}
ConfigurationPropertySources.attach(environment);
return environment;
}
遍历调用所有SpringApplicationRunListener的enviromentPrepared()
方法就是宣告当前SpringBoot应用使用的Enviroment准备好了
5. 根据环境信息配置要忽略的bean信息
1 | private void configureIgnoreBeanInfo(ConfigurableEnvironment environment) { |
6. Banner配置SpringBoot彩蛋
打印banner标志,就是启动SpringBoot项目时出现的Spring
字样,当然我们也可以自定义banner,这里就不多说了1
2
3
4
5
6
7
8
9
10
11
12
13private Banner printBanner(ConfigurableEnvironment environment) {
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);
}
return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
}
7. 创建ApplicationContext应用上下文
createApplicationContext()
根据用户是否明确设置了applicationContextClass类型以及SpringApplication初始化阶段的推断结果,决定该为当前SpringBoot应用创建什么类型的ApplicationContext并创建完成。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
+ "annotation.AnnotationConfigApplicationContext";
public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework.boot."
+ "web.servlet.context.AnnotationConfigServletWebServerApplicationContext";
private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
"org.springframework.web.context.ConfigurableWebApplicationContext" };
protected ConfigurableApplicationContext createApplicationContext() {
//用户是否明确设置了applicationContextClass,在SpringApplication中有对应的setter方法
Class<?> contextClass = this.applicationContextClass;
//如果没有主动设置
if (contextClass == null) {
try {
//判断当前应用的类型,也就是之前SpringApplication初始化阶段的推断结果
switch (this.webApplicationType) {
//servlet应用程序
case SERVLET:
contextClass = Class.forName(DEFAULT_WEB_CONTEXT_CLASS);
break;
//reactive响应式程序
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);
}
在SpringBoot官网对ApplicationContext的类型是如下定义的:
- 当SpringMVC存在的时候,就使用AnnotationConfigServletWebServerApplicationContext
- 当SpringMVC不存在的时候,Spring WebFlux响应式存在的时候,使用AnnotationConfigReactiveWebServerApplicationContext
- 如果以上都不是,默认就用AnnotationConfigApplicationContext
- SpringApplication存在设置ApplicationContext的方法,在JUnit测试中使用SpringApplication通常要设置ApplicationContext
8. 加载SpringBootExceptionReporter
1 | exceptionReporters = getSpringFactoriesInstances( |
1 | private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, |
这里也是通过SpringFactoriesLoader加载META-INF/spring.factories中key为SpringBootExceptionReporter的全类名的value值
SpringBootExceptionReporter
是一个回调接口,用于支持对SpringApplication
启动错误的自定义报告。里面就一个报告启动失败的方法- 其实现类:
org.springframework.boot.diagnostics.FailureAnalyzers
用于触发从spring.factories加载的FailureAnalyzer
和FailureAnalysisReporter
实例
9. ApplicationContext基本属性配置
1 | private void prepareContext(ConfigurableApplicationContext context, |
1). applyInitializers(context);
1 | protected void applyInitializers(ConfigurableApplicationContext context) { |
遍历调用这些ApplicationContextInitializer的initialize(applicationContext)方法来对已经创建好的ApplicationContext进行进一步的处理
2). load(ApplicationContext context, Object[] sources)
1 | protected void load(ApplicationContext context, Object[] sources) { |
设置资源加载器,加载各种beans到ApplicationContext对象中
10. 更新应用上下文
1 | private void refreshContext(ConfigurableApplicationContext context) { |
进入内部的refresh()方法,准备环境所需的bean工厂,通过工厂产生环境所需的bean,重点就是产生bean1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
11. afterRefresh()
上下文刷新后调用该方法,目前没有操作1
2
3protected void afterRefresh(ConfigurableApplicationContext context,
ApplicationArguments args) {
}
12. callRunner()
1 | private void callRunners(ApplicationContext context, ApplicationArguments args) { |
查找当前的ApplicationContext中是否注册有CommandLineRunner或者ApplicationRunner,如果有,就遍历执行他们。
SpringBoot启动流程总结
上面从SpringApplication的初始化到SpringApplication.run()方法执行,基本上按照其内部函数调用的顺序一步一步分析下来,内容非常多,很容易把人搞晕。在网上发现一张图,图出自SpringBoot启动流程解析,画的比较清楚明白,把SpringBoot启动整个流程都包含进来了
再总结下run方法中最关键的几步:
- 加载SpringApplicationRunListeners监听器
- 配置环境模块
- 创建ApplicationContext应用上下文
- ApplicationContext基本属性配置
- 更新应用上下文,产生环境所需要的bean
小结
上面的分析都是基于SpringBoot2.0版本,在之前的版本,内容上可能有些偏差,大体思路是差不多的。在阅读源码的过程中,看了很多前人分析的博客文章,也借鉴了他们的分析流程,有点「前人栽树,后人乘凉」的感觉,现在「取之网络,再回馈之网络」