在Spring Boot
启动初探的文章中,我们了解到@SpringBootApplication
注解起到的作用。接下来我们探究一下SpringApplication.run()
方法,在应用启动的背后都做了哪些事情。让我对Spring Boot
的了解更深一层。
演示环境
- IntelliJ IDEA 2018.2.5 (Ultimate Edition)
- JDK 1.8
- Spring Boot 2.1.1.RELEASE
- Maven 3.5.4
初始化Spring Boot
项目
初始化项目我们就不多阐述了,大家自行解决吧。当我们初始完项目后都会有一个类似的启动类,代码如下:
@SpringBootApplication
public class JeromeApplication {
public static void main(String[] args) {
SpringApplication.run(JeromeApplication.class, args);
}
}
启动项目就是运行这个main
方法就行了,接下来我们就以SpringApplication.run(JeromeApplication.class, args);
作为入口,来探究一下项目启动的时候Spring Boot
到底做了些什么。
进入源码部分
当我们使用工具跟run
方法的时候,会走到它一个重载方法在1258行:
// 代码跟到这儿,会调用run方法的重载方法
public static ConfigurableApplicationContext run(Class<?> primarySource,
String... args) {
return run(new Class<?>[] { primarySource }, args);
}
// 实际运行这个方法
public static ConfigurableApplicationContext run(Class<?>[] primarySources,
String[] args) {
return new SpringApplication(primarySources).run(args);
}
这个方法里面首先要创建一个SpringApplication
对象实例,然后调用这个创建好的SpringApplication
的实例的run
方法。
探究SpringApplication
的构造阶段
当我们使用默认的方式启动项目,我们可以根据上面那段代码看出通过new SpringApplication(primarySources)
创建了一个对象,我们进入到这个构造方法然后再跳到重载方法(263行):
// 同样会运行重载方法
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、进行Web应用的类型推断
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 2、加载应用上下文初始化器
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
// 3、加载应用事件监听器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 4、推断引导类
this.mainApplicationClass = deduceMainApplicationClass();
}
根据上面的代码我们来说一下构造阶段主要具体都干了写什么。
1、进行Web应用的类型推断
根据WebApplicationType
这个类的静态方法deduceFromClasspath()
推断Web应用类型,其实也就是根据当前的应用 ClassPath
中是否存在相关实现类来推断 Web 应用的类型,类型有三种:
- Web Reactive:
WebApplicationType.REACTIVE
- Web Servlet:
WebApplicationType.SERVLET
- 非 Web:
WebApplicationType.NONE
// 类的全路径的静态常量
private static final String[] SERVLET_INDICATOR_CLASSES = { "javax.servlet.Servlet",
"org.springframework.web.context.ConfigurableWebApplicationContext" };
private static final String WEBMVC_INDICATOR_CLASS = "org.springframework."
+ "web.servlet.DispatcherServlet";
private static final String WEBFLUX_INDICATOR_CLASS = "org."
+ "springframework.web.reactive.DispatcherHandler";
private static final String JERSEY_INDICATOR_CLASS = "org.glassfish.jersey.servlet.ServletContainer";
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;
}
}
return WebApplicationType.SERVLET;
}
根据上面的代码可以看出推断的逻辑:
- 当
WEBFLUX_INDICATOR_CLASS
存在并且WEBMVC_INDICATOR_CLASS
和JERSEY_INDICATOR_CLASS
都不存在的时候,Web的类型才会被推断成Web Reactive。 - 当
WEBFLUX_INDICATOR_CLASS
和SERVLET_INDICATOR_CLASSES
都不存在的时候,Web的类型才会被推断成非 Web类型。 - 其它情况都会被推断成Web Servlet的类型,所以当Reactive和Servlet同时存在的时候,Reactive会被覆盖掉。
2、加载应用上下文初始化器
利用 Spring 工厂加载机制,实例化 ApplicationContextInitializer
实现类,并排序对象集合。
// 在这里传入的是ApplicationContextInitializer.class
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();
// 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
来加载spring-boot-autoconfigure-2.1.1.RELEASE.jar
包里面的META-INF/spring.factories
配置文件,来读取key为ApplicationContextInitializer.class
的全路径名org.springframework.context.ApplicationContextInitializer
配置的值,然后再根据这些names
去实例化这些对象,最后在根据Order
进行排序。实现Order
的两种方式:
- 实现
org.springframework.core.Ordered
接口,重写int getOrder();
方法。 - 使用
@Order
注解进行标注。
3、加载应用事件监听器
利用 Spring 工厂加载机制,实例化 ApplicationListener
实现类,并排序对象集合。其实这个和上下文的初始化器的加载时同样的原理。
// 在这里传入的是ApplicationListener.class
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();
// 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
来加载spring-boot-autoconfigure-2.1.1.RELEASE.jar
包里面的META-INF/spring.factories
配置文件,来读取key为ApplicationListener.class
的全路径名org.springframework.context.ApplicationListener
配置的值,然后再根据这些names
去实例化这些对象,最后在根据Order
进行排序。
4、推断引导类
利用异常的堆栈信息来推断引导类,也就是我们启动Spring Boot
项目的那个main
方法所在的类。
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;
}
通过异常的堆栈信息中方法名为main的栈帧来得到入口类的名字。
到这里SpringApplication
就已经完成了在构造阶段所初始化的过程。
探究SpringApplication
的运行阶段
当我们执行完new SpringApplication(primarySources)
后,就会调用该对象的run
方法。
public ConfigurableApplicationContext run(String... args) {
// 启动一个计时器
StopWatch stopWatch = new StopWatch();
// 开始计时
stopWatch.start();
// 定义局部变量
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
// 设置java.awt.headless系统属性为true - 没有图形化界面
configureHeadlessProperty();
// 1、加载SpringApplication运行监听器
SpringApplicationRunListeners listeners = getRunListeners(args);
// 2、运行SpringApplication运行监听器,Spring应用刚启动
listeners.starting();
try {
// 根据传入的args,创建ApplicationArguments对象
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
// 4、创建 Environment,这里调用listeners.environmentPrepared(ConfigurableEnvironment)
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
configureIgnoreBeanInfo(environment);
// 打印Banner
Banner printedBanner = printBanner(environment);
// 5、创建Spring应用上下文
context = createApplicationContext();
// 异常分析报告的记录容器的初始化
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// Spring上下文前置处理,这里调用listeners.contextPrepared(context)和listeners.contextLoaded(context)
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
// Spring上下文刷新
refreshContext(context);
// Spring上下文后置处理
afterRefresh(context, applicationArguments);
// 停止计时
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
// Spring 应用已经启动
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
// 处理异常,并会打印异常分析报告,并调用listeners.failed(context, exception);
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
// Spring 应用正在运行
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
1、加载 SpringApplication
运行监听器
这个加载运行监听器的方式,大家仔细一看就会发现,和初始化阶段加载应用上下文初始化器和事件监听器的方法如出一辙。
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 = getClassLoader();
// 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;
}
通过上面的代码也可以看出,同样是利用的工厂加载机制读取SpringApplicationRunListener.class
的全路径名org.springframework.boot.SpringApplicationRunListener
配置的值,然后再根据这些names
去实例化这些对象,最后在根据Order
进行排序。获取这些对象的集合后会被封装到SpringApplicationRunListeners
这个类里面。
2、运行 SpringApplication
运行监听器
在这里SpringApplicationRunListener
监听了多个运行状态方法,参照下表:
监听方法 | 阶段说明 | Spring Boot 起始版本 |
---|---|---|
starting() | Spring 应用刚启动 | 1.0 |
environmentPrepared(ConfigurableEnvironment) | ConfigurableEnvironment 准备妥当,允许将其调整 | 1.0 |
contextPrepared(ConfigurableApplicationContext) | ConfigurableApplicationContext 准备妥当,允许将其调整 | 1.0 |
contextLoaded(ConfigurableApplicationContext) | ConfigurableApplicationContext 已装载,但仍未启动 | 1.0 |
started(ConfigurableApplicationContext) | ConfigurableApplicationContext 已启动,此时 Spring Bean 已初始化完成 | 2.0 |
running(ConfigurableApplicationContext) | Spring 应用正在运行 | 2.0 |
failed(ConfigurableApplicationContext,Throwable) | Spring 应用运行失败 | 2.0 |
3、监听Spring Boot
事件
我们在加载阶段,通过工厂机制加载了 SpringApplicationRunListener
的实现类 EventPublishingRunListener
,这个类利用SimpleApplicationEventMulticaster
来广播 Spring Boot 事件。
EventPublishingRunListener
监听方法与 Spring Boot
事件对照表:
监听方法 | Spring Boot 事件 | Spring Boot 起始版本 |
---|---|---|
starting() | ApplicationStartingEvent | 1.5.0 |
environmentPrepared(ConfigurableEnvironment) | ApplicationEnvironmentPreparedEvent | 1.0 |
contextPrepared(ConfigurableApplicationContext) | ApplicationContextInitializedEvent | 2.1.0 |
contextLoaded(ConfigurableApplicationContext) | ApplicationPreparedEvent | 1.0 |
started(ConfigurableApplicationContext) | ApplicationStartedEvent | 2.0.0 |
running(ConfigurableApplicationContext) | ApplicationReadyEvent | 1.3.0 |
failed(ConfigurableApplicationContext,Throwable) | ApplicationFailedEvent | 1.0 |
4、创建 Environment
根据构造阶段的推断出来的Web应用类型创建对应的 ConfigurableEnvironment
实例:
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.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader())
.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
return environment;
}
private Class<? extends StandardEnvironment> deduceEnvironmentClass() {
switch (this.webApplicationType) {
case SERVLET:
return StandardServletEnvironment.class;
case REACTIVE:
return StandardReactiveWebEnvironment.class;
default:
return StandardEnvironment.class;
}
}
- Web Reactive:
StandardEnvironment
- Web Servlet:
StandardServletEnvironment
- 非 Web:
StandardEnvironment
5、创建 Spring 应用上下文
根据构造阶段的推断出来的Web应用类型创建对应的 ConfigurableApplicationContext
实例。
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
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);
}
}
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}
- Web Reactive:
AnnotationConfigReactiveWebServerApplicationContext
- Web Servlet:
AnnotationConfigServletWebServerApplicationContext
- 非 Web:
AnnotationConfigApplicationContext
总结
至此我们就完成了对Spring Boot
项目启动的流程进行了简要的分析。我们可以知道SpringApplication
主要分为两个阶段,一个是准备阶段主要进行Web类型推断、引导类推断、加载上下文初始化器和事件监听器,另一个是运行阶段主要进行加载运行SpringApplication
的运行监听器、监听Spring Boot
事件、创建Environment
、创建Spring应用上下文。这样就完成了一个Spring Boot
应用的启动。