springboot系列文章之SpringApplication详解

Catalogue
  1. 1. 前言
  2. 2. SpringApplication的初始化
    1. 2.1. 1. 推断应用类型
    2. 2.2. 2. 加载初始化构造器ApplicationContextInitializer
    3. 2.3. 3. 创建应用监听器
    4. 2.4. 4. 设置应用main()方法所在的类
  3. 3. SpringApplication的run方法
    1. 3.1. 1. Headless模式设置
    2. 3.2. 2. 加载SpringApplicationRunListeners监听器
    3. 3.3. 3. 封装ApplicationArguments对象
    4. 3.4. 4. 配置环境模块
    5. 3.5. 5. 根据环境信息配置要忽略的bean信息
    6. 3.6. 6. Banner配置SpringBoot彩蛋
    7. 3.7. 7. 创建ApplicationContext应用上下文
    8. 3.8. 8. 加载SpringBootExceptionReporter
    9. 3.9. 9. ApplicationContext基本属性配置
      1. 3.9.1. 1). applyInitializers(context);
      2. 3.9.2. 2). load(ApplicationContext context, Object[] sources)
    10. 3.10. 10. 更新应用上下文
    11. 3.11. 11. afterRefresh()
    12. 3.12. 12. callRunner()
  4. 4. SpringBoot启动流程总结
  5. 5. 小结
  6. 6. 参考资料 & 鸣谢

前言

还是从SpringBoot的启动类说起,这篇文章主要分析启动类中的SpringApplication

1
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
17
public 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中定义了这三种类型
webApplication
web

2. 加载初始化构造器ApplicationContextInitializer

setInitializers((Collection)getSpringFactoriesInstances(ApplicationContextInitializer.class)): 通过SpringFactoriesLoader在应用的classpath中查找并加载所有可用的ApplicationContextInitializer
init
进入loadFactoryNames方法,然后进入loadSpringFactories方法,获取当前ClassLoader下的所有META-INF/spring.factories文件的配置信息

而后通过loadSpringFactories(classloader).getOrDefault(factoryClassName,Collections.emptyList()) 从所有META-INF/spring.factories文件的配置信息的map中获取指定的factory的值
laod
spring
默认情况下,从 spring.factories 文件找出的 key 为 ApplicationContextInitializer 的类有如上图中所示4种

对于 ApplicationContextInitializer,它是应用程序初始化器,做一些初始化工作

1
2
3
4
5
6
7
8
9
public 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中查找并加载所有可用的ApplicationListener

1
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

a
ApplicationListener,应用程序事件(ApplicationEvent)监听器:

1
2
3
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
void onApplicationEvent(E event);
}

更详细的分析可以参阅我之前的文章: springboot系列文章之启动时初始化数据

4. 设置应用main()方法所在的类

在SpringApplication构造函数的最后一步,根据调用栈推断并设置main方法的定义类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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的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
62
public 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
    6
    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)));
    }

2. 加载SpringApplicationRunListeners监听器

1
SpringApplicationRunListeners listeners = getRunListeners(args);

getRunListeners(args)也是通过 SpringFactoriesLoaderMETA-INF/spring.factories查找到并加载的SpringApplicationRunListener。该类实际上是监听SpringApplication的run方法的执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private 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所联系起来的
startup

更详细的分析请参阅 :SpringBoot源码分析之SpringBoot的启动过程

3. 封装ApplicationArguments对象

将args参数封装成 ApplicationArguments 对象

1
2
3
4
5
public DefaultApplicationArguments(String[] args) {
Assert.notNull(args, "Args must not be null");
this.source = new Source(args);
this.args = args;
}

官网对 ApplicationArguments 的解释如下
args

4. 配置环境模块

根据listenersapplicationArguments 创建并配置当前SpringBoot应用将要使用的Enviroment

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private 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
2
3
4
5
6
7
8
9
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());
}
}

6. Banner配置SpringBoot彩蛋

打印banner标志,就是启动SpringBoot项目时出现的Spring字样,当然我们也可以自定义banner,这里就不多说了

1
2
3
4
5
6
7
8
9
10
11
12
13
private 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
38
public 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的类型是如下定义的:
Applicaiton

  • 当SpringMVC存在的时候,就使用AnnotationConfigServletWebServerApplicationContext
  • 当SpringMVC不存在的时候,Spring WebFlux响应式存在的时候,使用AnnotationConfigReactiveWebServerApplicationContext
  • 如果以上都不是,默认就用AnnotationConfigApplicationContext
  • SpringApplication存在设置ApplicationContext的方法,在JUnit测试中使用SpringApplication通常要设置ApplicationContext

8. 加载SpringBootExceptionReporter

1
2
3
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
1
2
3
4
5
6
7
8
9
10
11
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
Set<String> names = new LinkedHashSet<>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}

这里也是通过SpringFactoriesLoader加载META-INF/spring.factories中key为SpringBootExceptionReporter的全类名的value值

  • SpringBootExceptionReporter是一个回调接口,用于支持对SpringApplication启动错误的自定义报告。里面就一个报告启动失败的方法
  • 其实现类:org.springframework.boot.diagnostics.FailureAnalyzers
    用于触发从spring.factories加载的FailureAnalyzerFailureAnalysisReporter实例

9. 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
private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
//设置应用的环境
context.setEnvironment(environment);
//对 context 进行了预设置
postProcessApplicationContext(context);
applyInitializers(context);
遍历调用SpringApplicationRunListener的contextPrepared()方法,通告SpringBoot应用使用的ApplicationContext准备好了
listeners.contextPrepared(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}

// Add boot specific singleton beans
context.getBeanFactory().registerSingleton("springApplicationArguments",
applicationArguments);
if (printedBanner != null) {
context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
}

// Load the sources
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
load(context, sources.toArray(new Object[0]));
//遍历调用SpringApplicationRunListener的contextLoaded()方法,通告ApplicationContext装填完毕
listeners.contextLoaded(context);
}

1). applyInitializers(context);

1
2
3
4
5
6
7
8
protected void applyInitializers(ConfigurableApplicationContext context) {   
for (ApplicationContextInitializer initializer : getInitializers()) {
Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(
initializer.getClass(), ApplicationContextInitializer.class);
Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
initializer.initialize(context);
}
}

遍历调用这些ApplicationContextInitializer的initialize(applicationContext)方法来对已经创建好的ApplicationContext进行进一步的处理

2). load(ApplicationContext context, Object[] sources)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
protected void load(ApplicationContext context, Object[] sources) {
if (logger.isDebugEnabled()) {
logger.debug(
"Loading source " + StringUtils.arrayToCommaDelimitedString(sources));
}
BeanDefinitionLoader loader = createBeanDefinitionLoader(
getBeanDefinitionRegistry(context), sources);
if (this.beanNameGenerator != null) {
loader.setBeanNameGenerator(this.beanNameGenerator);
}
if (this.resourceLoader != null) {
loader.setResourceLoader(this.resourceLoader);
}
if (this.environment != null) {
loader.setEnvironment(this.environment);
}
loader.load();
}

设置资源加载器,加载各种beans到ApplicationContext对象中

10. 更新应用上下文

1
2
3
4
5
6
7
8
9
10
11
private void refreshContext(ConfigurableApplicationContext context) {
refresh(context);
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
}

进入内部的refresh()方法,准备环境所需的bean工厂,通过工厂产生环境所需的bean,重点就是产生bean

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
@Override
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
3
protected void afterRefresh(ConfigurableApplicationContext context,
ApplicationArguments args) {
}

12. callRunner()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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);
}
}
}

查找当前的ApplicationContext中是否注册有CommandLineRunner或者ApplicationRunner,如果有,就遍历执行他们。

SpringBoot启动流程总结

上面从SpringApplication的初始化到SpringApplication.run()方法执行,基本上按照其内部函数调用的顺序一步一步分析下来,内容非常多,很容易把人搞晕。在网上发现一张图,图出自SpringBoot启动流程解析,画的比较清楚明白,把SpringBoot启动整个流程都包含进来了
appl
再总结下run方法中最关键的几步:

  • 加载SpringApplicationRunListeners监听器
  • 配置环境模块
  • 创建ApplicationContext应用上下文
  • ApplicationContext基本属性配置
  • 更新应用上下文,产生环境所需要的bean

    小结

    上面的分析都是基于SpringBoot2.0版本,在之前的版本,内容上可能有些偏差,大体思路是差不多的。在阅读源码的过程中,看了很多前人分析的博客文章,也借鉴了他们的分析流程,有点「前人栽树,后人乘凉」的感觉,现在「取之网络,再回馈之网络」

参考资料 & 鸣谢

Bagikan Komentar