精尽Spring Boot源码分析 – SpringApplication 启动类的启动过程
该系列文章是笔者在学习 Spring Boot 过程中总结下来的,里面涉及到相关源码,可能对读者不太友好,请结合我的源码注释 Spring Boot 源码分析 GitHub 地址 进行阅读
Spring Boot 版本:2.2.x
最好对 Spring 源码有一定的了解,可以先查看我的 《死磕 Spring 之 IoC 篇 – 文章导读》 系列文章
如果该篇内容对您有帮助,麻烦点击一下“推荐”,也可以关注博主,感激不尽~
该系列其他文章请查看:《精尽 Spring Boot 源码分析 – 文章导读》
概述
我们 Spring Boot 项目的启动类通常有下面三种方式
// 方式一
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
// 方式二
@SpringBootApplication
public class Application {
public static void main(String[] args) {
new SpringApplicationBuilder(Application.class).run(args);
}
}
// 方式三
@SpringBootApplication
public class Application extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(Application.class);
}
}
方式一和方式二本质上都是通过调用 SpringApplication#run(..)
方法来启动应用,不同的是方式二通过构建器模式,先构建一个 SpringApplication
实例对象,然后调用其 run(..)
方法启动应用,这种方式可以对 SpringApplication
进行配置,更加的灵活。
我们再来看到方式三,它和方式一差不多,不同的是它继承了 SpringBootServletInitializer
这个类,作用就是当你的 Spring Boot 项目打成 war
包时能够放入外部的 Tomcat 容器中运行,如果是 war
包,那上面的 main(...)
方法自然是不需要的,当然,configure(..)
方法也可以不要。
在上篇 《精尽 Spring Boot 源码分析 – Jar 包的启动实现》 文章中讲到,通过 java -jar
启动 Spring Boot 应用时,最终会调用我们启动类的 main(..)
方法,那么本文主要就是对 SpringApplication
这个类进行分析。至于上面 @SpringBootApplication
注解和方式三的相关内容在后续的文章会讲到。
SpringApplicationBuilder
org.springframework.boot.builder.SpringApplicationBuilder
,SpringApplication 的构建器,如下:
public class SpringApplicationBuilder {
private final SpringApplication application;
private ConfigurableApplicationContext context;
private final AtomicBoolean running = new AtomicBoolean(false);
private final Set<Class<?>> sources = new LinkedHashSet<>();
public SpringApplicationBuilder(Class<?>... sources) {
this.application = createSpringApplication(sources);
}
protected SpringApplication createSpringApplication(Class<?>... sources) {
return new SpringApplication(sources);
}
public ConfigurableApplicationContext run(String... args) {
if (this.running.get()) {
// If already created we just return the existing context
return this.context;
}
configureAsChildIfNecessary(args);
if (this.running.compareAndSet(false, true)) {
synchronized (this.running) {
// If not already running copy the sources over and then run.
this.context = build().run(args);
}
}
return this.context;
}
public SpringApplication build() {
return build(new String[0]);
}
public SpringApplication build(String... args) {
configureAsChildIfNecessary(args);
this.application.addPrimarySources(this.sources);
return this.application;
}
}
上面仅列出了 SpringApplicationBuilder
的部分代码,它支持对 SpringApplication
进行配置,底层还是通过 SpringApplication
这个类来启动应用的,不过多的讲述,感兴趣的可以去看看。
SpringApplication
org.springframework.boot.SpringApplication
,Spring 应用启动器。正如其代码上所添加的注释,它来提供启动 Spring 应用的功能。
Class that can be used to bootstrap and launch a Spring application from a Java main method.
相关属性
public class SpringApplication {
/**
* Spring 应用上下文(非 Web 场景)
* The class name of application context that will be used by default for non-web environments.
*/
public static final String DEFAULT_CONTEXT_CLASS = "org.springframework.context."
+ "annotation.AnnotationConfigApplicationContext";
/**
* Spring 应用上下文(Web 场景)
* The class name of application context that will be used by default for web environments.
*/
public static final String DEFAULT_SERVLET_WEB_CONTEXT_CLASS = "org.springframework.boot."
+ "web.servlet.context.AnnotationConfigServletWebServerApplicationContext";
/**
* Spring 应用上下文(Reactive 场景)
* The class name of application context that will be used by default for reactive web environments.
*/
public static final String DEFAULT_REACTIVE_WEB_CONTEXT_CLASS = "org.springframework."
+ "boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext";
/**
* 主 Bean(通常为我们的启动类,优先注册)
*/
private Set<Class<?>> primarySources;
/**
* 来源 Bean(优先注册)
*/
private Set<String> sources = new LinkedHashSet<>();
/**
* 启动类
*/
private Class<?> mainApplicationClass;
/**
* Banner 打印模式
*/
private Banner.Mode bannerMode = Banner.Mode.CONSOLE;
/**
* 是否打印应用启动耗时日志
*/
private boolean logStartupInfo = true;
/**
* 是否接收命令行中的参数
*/
private boolean addCommandLineProperties = true;
/**
* 是否设置 ConversionService 类型转换器
*/
private boolean addConversionService = true;
/**
* Banner 对象(用于输出横幅)
*/
private Banner banner;
/**
* 资源加载对象
*/
private ResourceLoader resourceLoader;
/**
* Bean 名称生成器
*/
private BeanNameGenerator beanNameGenerator;
/**
* Spring 应用的环境对象
*/
private ConfigurableEnvironment environment;
/**
* Spring 应用上下文的 Class 对象
*/
private Class<? extends ConfigurableApplicationContext> applicationContextClass;
/**
* Web 应用的类型(Servlet、Reactive)
*/
private WebApplicationType webApplicationType;
/**
* 是否注册钩子函数,用于 JVM 关闭时关闭 Spring 应用上下文
*/
private boolean registerShutdownHook = true;
/**
* 保存 ApplicationContextInitializer 对象(主要是对 Spring 应用上下文做一些初始化工作)
*/
private List<ApplicationContextInitializer<?>> initializers;
/**
* 保存 ApplicationListener 监听器(支持在整个 Spring Boot 的多个时间点进行扩展)
*/
private List<ApplicationListener<?>> listeners;
/**
* 默认的配置项
*/
private Map<String, Object> defaultProperties;
/**
* 额外的 profile
*/
private Set<String> additionalProfiles = new HashSet<>();
/**
* 是否允许覆盖 BeanDefinition
*/
private boolean allowBeanDefinitionOverriding;
/**
* 是否为自定义的 Environment 对象
*/
private boolean isCustomEnvironment = false;
/**
* 是否支持延迟初始化(需要通过 {@link LazyInitializationExcludeFilter} 过滤)
*/
private boolean lazyInitialization = false;
}
上面基本上列出了 SpringApplication
的所有属性,每个属性都比较关键,大家先有一个印象,后面也可以回过头来看
构造器
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
// <1> 设置资源加载器
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
// <2> 设置主要的 Class 类对象,通常是我们的启动类
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// <3> 通过 `classpath` 判断是否存在相应的 Class 类对象,来决定当前 Web 应用的类型(REACTIVE、SERVLET、NONE),默认为 **SERVLET**
// 不同的类型后续创建的 Environment 类型不同
this.webApplicationType = WebApplicationType.deduceFromClasspath();
/**
* <4> 初始化所有 `ApplicationContextInitializer` 类型的对象,并保存至 `initializers` 集合中
* 通过类加载器从 `META-INF/spring.factories` 文件中获取 ApplicationContextInitializer 类型的类名称,并进行实例化
*/
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
/**
* <5> 初始化所有 `ApplicationListener` 类型的对象,并保存至 `listeners` 集合中
* 通过类加载器从 `META-INF/spring.factories` 文件中获取 ApplicationListener 类型的类名称,并进行实例化
* 例如有一个 {@link ConfigFileApplicationListener}
*/
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// <6> 获取当前被调用的 `main` 方法所属的 Class 类对象,并设置(主要用于打印日志)
this.mainApplicationClass = deduceMainApplicationClass();
}
在我们自己的启动类中不管是通过哪种方式都是会先创建一个 SpringApplication
实例对象的,可以先看下它的 run(Class<?>, String...)
方法:
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class<?>[] { primarySource }, args);
}
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
// 同样是先创建一个 SpringApplication 对象
return new SpringApplication(primarySources).run(args);
}
实例化的过程中做了不少事情,如下:
-
设置资源加载器,默认为
null
,可以通过SpringApplicationBuilder
设置 -
设置
primarySources
为主要的 Class 类对象,通常是我们的启动类 -
通过
classpath
判断是否存在相应的 Class 类对象,来决定当前 Web 应用的类型(REACTIVE、SERVLET、NONE),默认为 SERVLET,不同的类型后续创建的 Environment 类型不同public enum WebApplicationType { NONE, SERVLET, REACTIVE; 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"; private static final String SERVLET_APPLICATION_CONTEXT_CLASS = "org.springframework.web.context.WebApplicationContext"; private static final String REACTIVE_APPLICATION_CONTEXT_CLASS = "org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext"; 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; } }
很简单,就是依次判断当前 JVM 中是否存在相关的 Class 类对象,来决定使用哪种 Web 类型,默认是 SERVLET 类型
-
初始化所有
ApplicationContextInitializer
类型的对象,并保存至initializers
集合中 -
初始化所有
ApplicationListener
类型的对象,并保存至listeners
集合中,例如 ConfigFileApplicationListener 和 LoggingApplicationListener -
获取当前被调用的
main
方法所属的 Class 类对象,并设置(主要用于打印日志)private Class<?> deduceMainApplicationClass() { try { // 获取当前的调用栈 StackTraceElement[] stackTrace = new RuntimeException().getStackTrace(); // 对调用栈进行遍历,找到 `main` 方法所在的 Class 类对象并返回 for (StackTraceElement stackTraceElement : stackTrace) { if ("main".equals(stackTraceElement.getMethodName())) { return Class.forName(stackTraceElement.getClassName()); } } } catch (ClassNotFoundException ex) { // Swallow and continue } return null; }
上面的第 4
和 5
步都是通过类加载器从 META-INF/spring.factories
文件中分别获取 ApplicationContextInitializer
和 ApplicationListener
类型的类名称,然后进行实例化,这个两种类型的对象都是对 Spring 的一种拓展,像很多框架整合 Spring Boot 都可以通过自定义的 ApplicationContextInitializer
对 ApplicationContext 进行一些初始化,通过 ApplicationListener
在 Spring 应用启动的不同阶段来织入一些功能
getSpringFactoriesInstances 方法
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) {
// <1> 获取类加载器
ClassLoader classLoader = getClassLoader();
// Use names and ensure unique to protect against duplicates
// <2> 通过类加载器从 `META-INF/spring.factories` 文件中获取类型为 `type` 的类名称
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// <3> 为上一步获取到的所有类名称创建对应的实例对象
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
// <4> 通过 `@Order` 注解进行排序
AnnotationAwareOrderComparator.sort(instances);
// <5> 返回排序后的 `type` 类型的实例对象
return instances;
}
过程比较简单,如下:
-
获取类加载器
-
通过类加载器从所有
META-INF/spring.factories
文件中获取类型为type
的类名称,这里的 SpringFactoriesLoader 是 Spring 中的一个类 -
为上一步获取到的所有类名称创建对应的实例对象
private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args, Set<String> names) { List<T> instances = new ArrayList<>(names.size()); // 遍历所有的类名称,依次创建实例对象,一并返回 for (String name : names) { try { // 获取对应的 Class 类对象 Class<?> instanceClass = ClassUtils.forName(name, classLoader); // 对 Class 类对象进行校验,判断类型是否匹配 Assert.isAssignable(type, instanceClass); // 获取指定入参类型(ConfigurableApplicationContext)的构造器 Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes); // 通过构造器创建一个实例对象 T instance = (T) BeanUtils.instantiateClass(constructor, args); instances.add(instance); } catch (Throwable ex) { throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex); } } return instances; }
-
通过
@Order
注解进行排序 -
返回排序后的
type
类型的实例对象
SpringApplication#run 方法
上面已经讲述了 SpringApplication
的实例化过程,那么接下来就是调用它的 run(String... args)
方法来启动 Spring 应用,该过程如下:
public ConfigurableApplicationContext run(String... args) {
// <1> 创建 StopWatch 对象并启动,主要用于统计当前方法执行过程的耗时
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
// <2> 设置 `java.awt.headless` 属性,和 AWT 相关,暂时忽略
configureHeadlessProperty();
// <3> 初始化所有 `SpringApplicationRunListener` 类型的对象,并全部封装到 `SpringApplicationRunListeners` 对象中
SpringApplicationRunListeners listeners = getRunListeners(args);
// <4> 启动所有的 `SpringApplicationRunListener` 监听器
// 例如 `EventPublishingRunListener` 会广播 ApplicationEvent 应用正在启动的事件,
// 它里面封装了所有的 `ApplicationListener` 对象,那么此时就可以通过它们做一些初始化工作,进行拓展
listeners.starting();
try {
// <5> 创建一个应用参数对象,将 `main(String[] args)` 方法的 `args` 参数封装起来,便于后续使用
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// <6> 准备好当前应用 Environment 环境,这里会加载出所有的配置信息,包括 `application.yaml` 和外部的属性配置
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
// <7> 打印 banner 内容
Banner printedBanner = printBanner(environment);
// <8> 对 `context` (Spring 上下文)进行实例化
// 例如 Servlet(默认)会创建一个 AnnotationConfigServletWebServerApplicationContext 实例对象
context = createApplicationContext();
// <9> 获取异常报告器,通过类加载器从 `META-INF/spring.factories` 文件中获取 SpringBootExceptionReporter 类型的类名称,并进行实例化
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// <10> 对 Spring 应用上下文做一些初始化工作,例如执行 ApplicationContextInitializer#initialize(..) 方法
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
/**
* <11> 刷新 Spring 应用上下文,在这里会完成所有 Spring Bean 的初始化,同时会初始化好 Servlet 容器,例如 Tomcat
* 这一步涉及到 Spring IoC 的所有内容,参考[死磕Spring之IoC篇 - Spring 应用上下文 ApplicationContext](https://www.cnblogs.com/lifullmoon/p/14453083.html)
* 在 {@link ServletWebServerApplicationContext#onRefresh()} 方法中会创建一个 Servlet 容器(默认为 Tomcat),也就是当前 Spring Boot 应用所运行的 Web 环境
*/
refreshContext(context);
// <12> 完成刷新 Spring 应用上下文的后置操作,空实现,扩展点
afterRefresh(context, applicationArguments);
// <13> 停止 StopWatch,统计整个 Spring Boot 应用的启动耗时,并打印
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
// <14> 对所有的 SpringApplicationRunListener 监听器进行广播,发布 ApplicationStartedEvent 应用已启动事件
listeners.started(context);
// <15> 回调 IoC 容器中所有 ApplicationRunner 和 CommandLineRunner 类型的启动器
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
// 处理异常,同时会将异常发送至上面第 `9` 步获取到的异常报告器
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
// <16> 对所有的 SpringApplicationRunListener 监听器进行广播,发布 ApplicationReadyEvent 应用已就绪事件
listeners.running(context);
}
catch (Throwable ex) {
// 处理异常,同时会将异常发送至上面第 `9` 步获取到的异常报告器
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
整个启动过程做了很多事情,主要过程如下:
-
创建 StopWatch 对象并启动,主要用于统计当前方法执行过程的耗时
-
设置
java.awt.headless
属性,和 AWT 相关,暂时忽略 -
调用
getRunListeners(..)
方法,初始化所有SpringApplicationRunListener
类型的对象,并全部封装到SpringApplicationRunListeners
对象中 -
启动所有的
SpringApplicationRunListener
监听器,例如EventPublishingRunListener
会广播 ApplicationEvent 应用正在启动的事件,它里面封装了所有的ApplicationListener
对象,那么此时就可以通过它们做一些初始化工作,进行拓展 -
创建一个 ApplicationArguments 应用参数对象,将
main(String[] args)
方法的args
参数封装起来,便于后续使用 -
调用
prepareEnvironment(..)
方法,准备好当前应用 Environment 环境,这里会加载出所有的配置信息,包括application.yaml
和外部的属性配置 -
调用
printBanner(..)
方法,打印 banner 内容 -
调用
createApplicationContext()
方法, 对context
(Spring 上下文)进行实例化,例如 Servlet(默认)会创建一个 AnnotationConfigServletWebServerApplicationContext 实例对象 -
获取异常报告器,通过类加载器从
META-INF/spring.factories
文件中获取 SpringBootExceptionReporter 类型的类名称,并进行实例化 -
调用
prepareContext(..)
方法,对 Spring 应用上下文做一些初始化工作,例如执行ApplicationContextInitializer#initialize(..)
方法 -
调用
refreshContext(..)
方法,刷新 Spring 应用上下文,在这里会完成所有 Spring Bean 的初始化,同时会初始化好 Servlet 容器,例如 Tomcat这一步涉及到 Spring IoC 的所有内容,参考 《死磕Spring之IoC篇 – Spring 应用上下文 ApplicationContext》
在
ServletWebServerApplicationContext#onRefresh()
方法中会创建一个 Servlet 容器(默认为 Tomcat),也就是当前 Spring Boot 应用所运行的 Web 环境 -
调用
afterRefresh(..)
方法,完成刷新 Spring 应用上下文的后置操作,空实现,扩展点 -
停止 StopWatch,统计整个 Spring Boot 应用的启动耗时,并打印
-
对所有的
SpringApplicationRunListener
监听器进行广播,发布 ApplicationStartedEvent 应用已启动事件,通常只有一个EventPublishingRunListener
对象 -
回调 IoC 容器中所有 ApplicationRunner 和 CommandLineRunner 类型的启动器,默认情况下没有,先暂时忽略
-
对所有的
SpringApplicationRunListener
监听器进行广播,发布 ApplicationReadyEvent 应用已就绪事件,通常只有一个EventPublishingRunListener
对象
启动 Spring 应用的整个主流程清晰明了,先准备好当前应用的 Environment 环境,然后创建 Spring ApplicationContext 应用上下文。
该方法的整个过程更多的细节在于上面每一步调用的方法,抽丝剥茧,对于非常复杂的地方会另起文章进行分析
3. getRunListeners 方法
getRunListeners(String[] args)
方法,初始化所有 SpringApplicationRunListener
类型的对象,并全部封装到 SpringApplicationRunListeners
对象中,如下:
private SpringApplicationRunListeners getRunListeners(String[] args) {
// 指定实例化对象所使用的构造器的入参类型
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
// 通过类加载器从 `META-INF/spring.factories` 文件中获取 SpringApplicationRunListener 类型的类名称,并进行实例化
return new SpringApplicationRunListeners(logger,
getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}
这里同样调用上面讲过的 getSpringFactoriesInstances(..)
方法,通过类加载器从 META-INF/spring.factories
文件中获取 SpringApplicationRunListener
类型的类名称,并进行实例化
最后会将它们全部封装到 SpringApplicationRunListeners 对象中,就是把一个 List 封装到一个对象中,不过默认情况只有一个 EventPublishingRunListener
对象,其内部又封装了 SpringApplication
中的所有 ApplicationListener
应用监听器们,例如 ConfigFileApplicationListener 和 LoggingApplicationListener
6. prepareEnvironment 方法
prepareEnvironment(SpringApplicationRunListeners, ApplicationArguments)
方法,准备好当前应用 Environment 环境,加载出所有的配置信息,包括 application.yaml
和外部的属性配置,如下:
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
// <1> 根据 Web 应用的类型创建一个 StandardEnvironment 对象 `environment`
ConfigurableEnvironment environment = getOrCreateEnvironment();
// <2> 为 `environment` 配置默认属性(如果有)并设置需要激活的 `profile` 们
configureEnvironment(environment, applicationArguments.getSourceArgs());
// <3> 将当前 `environment` 的 MutablePropertySources 封装成 SpringConfigurationPropertySources 添加到 MutablePropertySources 首部
ConfigurationPropertySources.attach(environment);
/**
* <4> 对所有的 SpringApplicationRunListener 广播 ApplicationEvent 应用环境已准备好的事件,这一步比较复杂
* 例如 Spring Cloud 的 BootstrapApplicationListener 监听到该事件会创建一个 ApplicationContext 作为当前 Spring 应用上下文的父容器,同时会读取 `bootstrap.yml` 文件的信息
* {@link ConfigFileApplicationListener} 监听到该事件然后去解析 `application.yml` 等应用配置文件的配置信息
*/
listeners.environmentPrepared(environment);
// <5> 将 `environment` 绑定到当前 SpringApplication 上
bindToSpringApplication(environment);
// <6> 如果不是自定义的 Environment 则需要根据 Web 应用类型转换成对应 Environment 类型
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
deduceEnvironmentClass());
}
// <7> 再次进行上面第 `3` 步的处理过程,防止上面几步对上面的 PropertySources 有修改
ConfigurationPropertySources.attach(environment);
// <8> 返回准备好的 `environment`
return environment;
}
该过程如下:
-
根据 Web 应用的类型创建一个 StandardEnvironment 对象
environment
private ConfigurableEnvironment getOrCreateEnvironment() { if (this.environment != null) { return this.environment; } switch (this.webApplicationType) { case SERVLET: return new StandardServletEnvironment(); case REACTIVE: return new StandardReactiveWebEnvironment(); default: return new StandardEnvironment(); } }
-
为
environment
配置默认属性(如果有)并设置需要激活的profile
们protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) { if (this.addConversionService) { // <1> 获取 ConversionService 类型转换器并设置到 Environment 对象中 ConversionService conversionService = ApplicationConversionService.getSharedInstance(); environment.setConversionService((ConfigurableConversionService) conversionService); } // <2> 配置属性源里面的属性 // 例如有默认属性则将他们添加至最后,命令行中的参数则添加至最前面 configurePropertySources(environment, args); // <3> 设置需要激活的 `profile` 们 // 例如通过 `-Dspring.profiles.active=dev` 配置需要激活的环境 configureProfiles(environment, args); } protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) { MutablePropertySources sources = environment.getPropertySources(); // 如果默认配置属性不为空则添加至最后 if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) { sources.addLast(new MapPropertySource("defaultProperties", this.defaultProperties)); } // 如果 `main` 方法有入参 if (this.addCommandLineProperties && args.length > 0) { String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME; if (sources.contains(name)) { PropertySource<?> source = sources.get(name); CompositePropertySource composite = new CompositePropertySource(name); composite.addPropertySource( new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args)); composite.addPropertySource(source); sources.replace(name, composite); } else { // 将命令行中的入参添加至最前面 // 例如 "java -jar xxx.jar --spring.profiles.active=dev",那么这里就可以获取到 sources.addFirst(new SimpleCommandLinePropertySource(args)); } } }
可以看到会将
main(String[] args)
方法入参中的--
开头的参数设置到 Environment 中 -
将当前
environment
的 MutablePropertySources 封装成 SpringConfigurationPropertySources 添加到 MutablePropertySources 首部 -
对所有的 SpringApplicationRunListener 广播 ApplicationEvent 应用环境已准备好的事件,这一步比较复杂,例如 Spring Cloud 的
BootstrapApplicationListener
监听到该事件会创建一个 ApplicationContext 作为当前 Spring 应用上下文的父容器,同时会读取bootstrap.yml
文件的信息这里会有一个
ConfigFileApplicationListener
监听到该事件然后去解析application.yml
等应用配置文件的配置信息 -
将
environment
绑定到当前 SpringApplication 上 -
如果不是自定义的 Environment 则需要根据 Web 应用类型转换成对应 Environment 类型
-
再次进行上面第
3
步的处理过程,防止上面几步对上面的 PropertySources 有修改 -
返回准备好的
environment
该方法准备好了当前应用 Environment 环境,主要在于上面第 4
步,是 ApplicationListener
监听器的一个扩展点,在这里会加载出所有的配置信息,包括 application.yml
和外部配置,解析配置的过程比较复杂,在后面的文章中单独分析
8. createApplicationContext 方法
createApplicationContext()
方法,对 context
(Spring 上下文)进行实例化,如下:
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
switch (this.webApplicationType) {
case SERVLET:
// org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext
contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
break;
case REACTIVE:
// org.springframework.boot.web.reactive.context.AnnotationConfigReactiveWebServerApplicationContext
contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
break;
default:
// org.springframework.context.annotation.AnnotationConfigApplicationContext
contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
}
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);
}
}
// 实例化这个 Class 对象
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}
不同的应用类型创建不同的 Spring 应用上下文对象:
- SERVLET(默认是这个):
AnnotationConfigServletWebServerApplicationContext
- REACTIVE:
AnnotationConfigReactiveWebServerApplicationContext
- DEFAULT:
AnnotationConfigApplicationContext
10. prepareContext 方法
prepareContext(ConfigurableApplicationContext, ConfigurableEnvironment, SpringApplicationRunListeners, ApplicationArguments, Banner)
方法,对 Spring 应用上下文做一些初始化工作,如下:
private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,
SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {
// <1> 为 Spring 应用上下文设置 Environment 环境
context.setEnvironment(environment);
// <2> 将一些工具 Bean 设置到 Spring 应用上下文中,供使用
postProcessApplicationContext(context);
// <3> 通知 ApplicationContextInitializer 对 Spring 应用上下文进行初始化工作
// 参考 SpringApplication 构造方法
applyInitializers(context);
// <4> 对所有 SpringApplicationRunListener 进行广播,发布 ApplicationContextInitializedEvent 初始化事件
listeners.contextPrepared(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// Add boot specific singleton beans
// <5> 向应用上下文注册 `main(String[])` 方法的参数 Bean 和 Banner 对象
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());
}
// Load the sources
// <6> 获取 `primarySources`(例如你的启动类)和 `sources`(例如 Spring Cloud 中的 @BootstrapConfiguration)源对象
Set<Object> sources = getAllSources();
// 应用上下文的源对象不能为空
Assert.notEmpty(sources, "Sources must not be empty");
// <7> 将上面的源对象加载成 BeanDefinition 并注册
load(context, sources.toArray(new Object[0]));
// <8> 对所有的 SpringApplicationRunListener 广播 ApplicationPreparedEvent 应用已准备事件
// 会把 ApplicationListener 添加至 Spring 应用上下文中
listeners.contextLoaded(context);
}
该过程如下:
-
为 Spring 应用上下文设置 Environment 环境
-
将一些工具 Bean 设置到 Spring 应用上下文中,供使用
protected void postProcessApplicationContext(ConfigurableApplicationContext context) { if (this.beanNameGenerator != null) { // 注册 Bean 名称生成器 context.getBeanFactory().registerSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR, this.beanNameGenerator); } if (this.resourceLoader != null) { if (context instanceof GenericApplicationContext) { // 设置资源加载器 ((GenericApplicationContext) context).setResourceLoader(this.resourceLoader); } if (context instanceof DefaultResourceLoader) { ((DefaultResourceLoader) context).setClassLoader(this.resourceLoader.getClassLoader()); } } if (this.addConversionService) { // 设置类型转换器 context.getBeanFactory().setConversionService(ApplicationConversionService.getSharedInstance()); } }
-
通知 ApplicationContextInitializer 对 Spring 应用上下文进行初始化工作
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."); // 执行 Spring 应用上下文初始器 // 例如 ContextIdApplicationContextInitializer 会向 Spring 应用上下文注册一个 ContextId 对象 initializer.initialize(context); } }
-
对所有 SpringApplicationRunListener 进行广播,发布 ApplicationContextInitializedEvent 初始化事件
-
向 Spring 应用上下文注册
main(String[])
方法的参数 Bean 和 Banner 对象 -
获取
primarySources
(例如你的启动类)和sources
(例如 Spring Cloud 中的@BootstrapConfiguration
)源对象,没有的话会抛出异常 -
将上面的源对象加载成 BeanDefinition 并注册
-
对所有的 SpringApplicationRunListener 广播 ApplicationPreparedEvent 应用已准备事件,会把 ApplicationListener 添加至 Spring 应用上下文中
通过上面的第 6
步你就知道为什么我们的启动类里面一定得有一个入参为启动类的 Class 对象了
11. refreshContext 方法
refreshContext(ConfigurableApplicationContext)
方法,刷新 Spring 应用上下文,在这里会完成所有 Spring Bean 的初始化,同时会初始化好 Servlet 容器,例如 Tomcat,该方法涉及到 Spring IoC 的所有内容,参考 《死磕Spring之IoC篇 – Spring 应用上下文 ApplicationContext》
private void refreshContext(ConfigurableApplicationContext context) {
if (this.registerShutdownHook) {
try {
// 为当前 Spring 应用上下文注册一个钩子函数
// 在 JVM 关闭时先关闭 Spring 应用上下文
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
// 调用 AbstractApplicationContext#refresh() 方法,刷新 Spring 上下文
refresh(context);
}
protected void refresh(ApplicationContext applicationContext) {
Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
((AbstractApplicationContext) applicationContext).refresh();
}
该方法主要是调用 AbstractApplicationContext#refresh()
方法,刷新 Spring 应用上下文,整个过程牵涉到 Spring 的所有内容,之前的一系列文章已经分析过,关于更多的细节这里不展开谈论,当然,这个过程会有对 @SpingBootApplication
注解的解析
根据 8. createApplicationContext 方法 方法中讲到,我们默认情况下是 SERVLET 应用类型,也就是创建一个 AnnotationConfigServletWebServerApplicationContext
对象,在其父类 ServletWebServerApplicationContext
中重写了 onRefresh()
方法,会创建一个 Servlet 容器(默认为 Tomcat),也就是当前 Spring Boot 应用所运行的 Web 环境,这部分内容放在后面的文章单独分析。
SpringApplicationRunListeners
org.springframework.boot.SpringApplicationRunListeners
,对 SpringApplicationRunListener 数组的封装
class SpringApplicationRunListeners {
private final Log log;
/**
* 封装的所有 SpringApplicationRunListener
* Spring Boot 在 META-INF/spring.factories 只配置 {@link EventPublishingRunListener} 监听器
*/
private final List<SpringApplicationRunListener> listeners;
SpringApplicationRunListeners(Log log, Collection<? extends SpringApplicationRunListener> listeners) {
this.log = log;
this.listeners = new ArrayList<>(listeners);
}
void starting() {
for (SpringApplicationRunListener listener : this.listeners) {
listener.starting();
}
}
void environmentPrepared(ConfigurableEnvironment environment) {
for (SpringApplicationRunListener listener : this.listeners) {
listener.environmentPrepared(environment);
}
}
void contextPrepared(ConfigurableApplicationContext context) {
for (SpringApplicationRunListener listener : this.listeners) {
listener.contextPrepared(context);
}
}
void contextLoaded(ConfigurableApplicationContext context) {
for (SpringApplicationRunListener listener : this.listeners) {
listener.contextLoaded(context);
}
}
void started(ConfigurableApplicationContext context) {
for (SpringApplicationRunListener listener : this.listeners) {
listener.started(context);
}
}
void running(ConfigurableApplicationContext context) {
for (SpringApplicationRunListener listener : this.listeners) {
listener.running(context);
}
}
void failed(ConfigurableApplicationContext context, Throwable exception) {
for (SpringApplicationRunListener listener : this.listeners) {
callFailedListener(listener, context, exception);
}
}
private void callFailedListener(SpringApplicationRunListener listener, ConfigurableApplicationContext context,
Throwable exception) {
try {
listener.failed(context, exception);
}
catch (Throwable ex) {
if (exception == null) {
ReflectionUtils.rethrowRuntimeException(ex);
}
if (this.log.isDebugEnabled()) {
this.log.error("Error handling failed", ex);
}
else {
String message = ex.getMessage();
message = (message != null) ? message : "no error message";
this.log.warn("Error handling failed (" + message + ")");
}
}
}
}
比较简单,就是封装了多个 SpringApplicationRunListener
对象,对于不同类型的事件,调用其不同的方法
# Run Listeners
org.springframework.boot.SpringApplicationRunListener=
org.springframework.boot.context.event.EventPublishingRunListener
可以在 META-INF/spring.factories
文件中看到,只有一个 EventPublishingRunListener 对象
EventPublishingRunListener
org.springframework.boot.context.event.EventPublishingRunListener
,实现了 SpringApplicationRunListener 接口,事件广播器,发布不同类型的事件
public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
/**
* Spring Boot 应用的启动类
*/
private final SpringApplication application;
/**
* 启动类 `main(String[])` 方法的入参
*/
private final String[] args;
/**
* 事件广播器,包含了所有的 `META-INF/spring.factories` 文件中配置的 {@link ApplicationListener} 监听器
*/
private final SimpleApplicationEventMulticaster initialMulticaster;
public EventPublishingRunListener(SpringApplication application, String[] args) {
this.application = application;
this.args = args;
this.initialMulticaster = new SimpleApplicationEventMulticaster();
// 在实例化 SpringApplication 的过程中会从 `META-INF/spring.factories` 文件中获取 ApplicationListener 类型的类名称,并进行实例化
// 这里会将他们添加至广播器中
for (ApplicationListener<?> listener : application.getListeners()) {
this.initialMulticaster.addApplicationListener(listener);
}
}
@Override
public int getOrder() {
return 0;
}
@Override
public void starting() {
// 广播 Spring Boot 应用正在启动事件
this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
}
@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
// 广播 Spring Boot 应用的 Environment 环境已准备事件
this.initialMulticaster
.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment));
}
@Override
public void contextPrepared(ConfigurableApplicationContext context) {
// 广播 Spring Boot 应用的 Spring 上下文已初始化事件
this.initialMulticaster
.multicastEvent(new ApplicationContextInitializedEvent(this.application, this.args, context));
}
@Override
public void contextLoaded(ConfigurableApplicationContext context) {
// 将所有的 ApplicationListener 添加至 Spring 应用上下文
for (ApplicationListener<?> listener : this.application.getListeners()) {
if (listener instanceof ApplicationContextAware) {
((ApplicationContextAware) listener).setApplicationContext(context);
}
context.addApplicationListener(listener);
}
// 广播 Spring Boot 应用的 Spring 上下文已准备事件
this.initialMulticaster.multicastEvent(new ApplicationPreparedEvent(this.application, this.args, context));
}
@Override
public void started(ConfigurableApplicationContext context) {
// 广播 Spring Boot 应用的 Spring 上下文已启动事件
context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context));
}
@Override
public void running(ConfigurableApplicationContext context) {
// 广播 Spring Boot 应用的 Spring 上下文准备就绪事件
context.publishEvent(new ApplicationReadyEvent(this.application, this.args, context));
}
@Override
public void failed(ConfigurableApplicationContext context, Throwable exception) {
ApplicationFailedEvent event = new ApplicationFailedEvent(this.application, this.args, context, exception);
if (context != null && context.isActive()) {
// Listeners have been registered to the application context so we should
// use it at this point if we can
context.publishEvent(event);
}
else {
// An inactive context may not have a multicaster so we use our multicaster to
// call all of the context"s listeners instead
if (context instanceof AbstractApplicationContext) {
for (ApplicationListener<?> listener : ((AbstractApplicationContext) context)
.getApplicationListeners()) {
this.initialMulticaster.addApplicationListener(listener);
}
}
this.initialMulticaster.setErrorHandler(new LoggingErrorHandler());
this.initialMulticaster.multicastEvent(event);
}
}
}
比较简单,关键在于内部的 SimpleApplicationEventMulticaster 事件广播器,里面包含了所有的 META-INF/spring.factories
文件中配置的 ApplicationListener
监听器,不同的方法发布不同的事件,进行广播
# Application Listeners
org.springframework.context.ApplicationListener=
org.springframework.boot.ClearCachesApplicationListener,
org.springframework.boot.builder.ParentContextCloserApplicationListener,
org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,
org.springframework.boot.context.FileEncodingApplicationListener,
org.springframework.boot.context.config.AnsiOutputApplicationListener,
org.springframework.boot.context.config.ConfigFileApplicationListener,
org.springframework.boot.context.config.DelegatingApplicationListener,
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,
org.springframework.boot.context.logging.LoggingApplicationListener,
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener
可以看到 Spring Boot 配置了许多个 ApplicationListener,后续文章会对 ConfigFileApplicationListener 和 LoggingApplicationListener 进行简单的分析
总结
Spring Boot 应用打成 jar
包后的启动都是通过 SpringApplication#run(String... args)
这个方法来启动整个 Spring 应用的,流程大致如下:
- 从
META-INF/spring.factories
文件中加载出相关 Class 对象,并进行实例化,例如ApplicationContextInitializer
,SpringApplicationRunListener
和ApplicationListener
对象 - 准备好当前 Spring 应用的 Environment 环境,这里会解析
application.yml
以及外部配置 - 创建一个 ApplicationContext 应用上下文对象,默认 SERVLET 类型下创建
AnnotationConfigServletWebServerApplicationContext
对象 - 调用
AbstractApplication#refresh()
方法,刷新 Spring 应用上下文,也就是之前一系列 Spring 相关的文章所讲述的内容
整个过程有许多个扩展点是通过监听器机制实现的,在不同阶段广播不同类型的事件,此时 ApplicationListener
就可进行相关的操作
在上面第 4
步中,SERVLET 应用类型下的 Spring 应用上下文会创建一个 Servlet 容器(默认为 Tomcat)
更多的细节在后续文章依次进行分析