Spring Boot启动过程源码分析--转
https://blog.csdn.net/dm_vincent/article/details/76735888
关于 Spring Boot,已经有很多介绍其如何使用的文章了,本文从源代码 (基于 Spring-boot 1.5.6) 的角度来看看 Spring Boot 的启动过程到底是怎么样的,为何以往纷繁复杂的配置到如今可以这么简便。
1. 入口类
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">main</span><span class="hljs-params">(String[] args)</span> {
SpringApplication.run(DemoApplication.class, args);
}
}
以上的代码就是通过 Spring Initializr 配置生成的一个最简单的 Web 项目 (只引入了 Web 功能) 的入口方法。这个想必只要是接触过 Spring Boot 都会很熟悉。简单的方法背后掩藏的是 Spring Boot 在启动过程中的复杂性,本文的目的就是一探这里面的究竟。
1.1 注解 @SpringBootApplication
而在看这个方法的实现之前,需要看看 @SpringBootApplication 这个注解的功能:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
// ...
}
很明显的,这个注解就是三个常用在一起的注解 @SpringBootConfiguration,@EnableAutoConfiguration 以及 @ComponentScan 的组合,并没有什么高深的地方。
1.1.1 @SpringBootConfiguration
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
}
这个注解实际上和 @Configuration 有相同的作用,配备了该注解的类就能够以 JavaConfig 的方式完成一些配置,可以不再使用 XML 配置。
1.1.2 @ComponentScan
顾名思义,这个注解完成的是自动扫描的功能,相当于 Spring XML 配置文件中的:
<context:component-scan>
可以使用 basePackages 属性指定要扫描的包,以及扫描的条件。如果不设置的话默认扫描 @ComponentScan 注解所在类的同级类和同级目录下的所有类,所以对于一个 Spring Boot 项目,一般会把入口类放在顶层目录中,这样就能够保证源码目录下的所有类都能够被扫描到。
1.1.3 @EnableAutoConfiguration
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({EnableAutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() <span class="hljs-keyword">default</span> {};
String[] excludeName() <span class="hljs-keyword">default</span> {};
}
这个注解是让 Spring Boot 的配置能够如此简化的关键性注解。目前知道这个注解的作用就可以了,关于自动配置不再本文讨论范围内,后面如果有机会另起文章专门分析这个自动配置的实现原理。
2. 入口方法
2.1 SpringApplication 的实例化
介绍完了入口类,下面开始分析关键方法:
SpringApplication.run(DemoApplication.class, args);
相应实现:
// 参数对应的就是 DemoApplication.class 以及 main 方法中的 args
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 的实例,然后运行它的 run 方法:
// 构造实例
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
this.webApplicationType = deduceWebApplicationType();
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
在构造函数中,主要做了 4 件事情:
2.1.1 推断应用类型是 Standard 还是 Web
private WebApplicationType deduceWebApplicationType() {
if (ClassUtils.isPresent(REACTIVE_WEB_ENVIRONMENT_CLASS, null)
&& !ClassUtils.isPresent(MVC_WEB_ENVIRONMENT_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
for (String className : WEB_ENVIRONMENT_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}
// 相关常量
private static final String REACTIVE_WEB_ENVIRONMENT_CLASS = "org.springframework."
+ "web.reactive.DispatcherHandler";
private static final String MVC_WEB_ENVIRONMENT_CLASS = "org.springframework."
+ "web.servlet.DispatcherServlet";
private static final String[] WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",
"org.springframework.web.context.ConfigurableWebApplicationContext" };
可能会出现三种结果:
- WebApplicationType.REACTIVE - 当类路径中存在 REACTIVE_WEB_ENVIRONMENT_CLASS 并且不存在 MVC_WEB_ENVIRONMENT_CLASS 时
- WebApplicationType.NONE - 也就是非 Web 型应用 (Standard 型),此时类路径中不包含 WEB_ENVIRONMENT_CLASSES 中定义的任何一个类时
- WebApplicationType.SERVLET - 类路径中包含了 WEB_ENVIRONMENT_CLASSES 中定义的所有类型时
2.1.2 设置初始化器 (Initializer)
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
这里出现了一个新的概念 - 初始化器。
先来看看代码,再来尝试解释一下它是干嘛的:
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
return getSpringFactoriesInstances(type, new Class<?>[] {});
}
// 这里的入参 type 就是 ApplicationContextInitializer.class
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// 使用 Set 保存 names 来避免重复元素
Set<String> names = new LinkedHashSet<>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// 根据 names 来进行实例化
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
// 对实例进行排序
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
这里面首先会根据入参 type 读取所有的 names(是一个 String 集合),然后根据这个集合来完成对应的实例化操作:
// 入参就是 ApplicationContextInitializer.class
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
try {
Enumeration<URL> urls = classLoader != null?classLoader.getResources("META-INF/spring.factories"):ClassLoader.getSystemResources("META-INF/spring.factories");
ArrayList result = new ArrayList();
<span class="hljs-keyword">while</span>(urls.<span class="hljs-title function_ invoke__">hasMoreElements</span>()) {
URL url = (URL)urls.<span class="hljs-title function_ invoke__">nextElement</span>();
Properties properties = PropertiesLoaderUtils.<span class="hljs-title function_ invoke__">loadProperties</span>(<span class="hljs-keyword">new</span> <span class="hljs-title class_">UrlResource</span>(url));
String factoryClassNames = properties.<span class="hljs-title function_ invoke__">getProperty</span>(factoryClassName);
result.<span class="hljs-title function_ invoke__">addAll</span>(Arrays.<span class="hljs-title function_ invoke__">asList</span>(StringUtils.<span class="hljs-title function_ invoke__">commaDelimitedListToStringArray</span>(factoryClassNames)));
}
<span class="hljs-keyword">return</span> result;
} catch (IOException var8) {
throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() + "] factories from location [" + "META-INF/spring.factories" + "]", var8);
}
}
这个方法会尝试从类路径的 META-INF/spring.factories 处读取相应配置文件,然后进行遍历,读取配置文件中 Key 为:org.springframework.context.ApplicationContextInitializer 的 value。以 spring-boot-autoconfigure 这个包为例,它的 META-INF/spring.factories 部分定义如下所示:
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.AutoConfigurationReportLoggingInitializer
因此这两个类名会被读取出来,然后放入到集合中,准备开始下面的实例化操作:
// 关键参数:
// type: org.springframework.context.ApplicationContextInitializer.class
// names: 上一步得到的names集合
private <T> List<T> createSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, ClassLoader classLoader, Object[] args,
Set<String> names) {
List<T> instances = new ArrayList<T>(names.size());
for (String name : names) {
try {
Class<?> instanceClass = ClassUtils.forName(name, classLoader);
Assert.isAssignable(type, instanceClass);
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;
}
初始化步骤很直观,没什么好说的,类加载,确认被加载的类确实是 org.springframework.context.ApplicationContextInitializer 的子类,然后就是得到构造器进行初始化,最后放入到实例列表中。
因此,所谓的初始化器就是 org.springframework.context.ApplicationContextInitializer 的实现类,这个接口是这样定义的:
public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {
<span class="hljs-comment">/**
* Initialize the given application context.
* <span class="hljs-doctag">@param</span> applicationContext the application to configure
*/</span>
<span class="hljs-keyword">void</span> <span class="hljs-title function_ invoke__">initialize</span>(C applicationContext);
}
根据类文档,这个接口的主要功能是:
在 Spring 上下文被刷新之前进行初始化的操作。典型地比如在 Web 应用中,注册 Property Sources 或者是激活 Profiles。Property Sources 比较好理解,就是配置文件。Profiles 是 Spring 为了在不同环境下 (如 DEV,TEST,PRODUCTION 等),加载不同的配置项而抽象出来的一个实体。
2.1.3. 设置监听器 (Listener)
设置完了初始化器,下面开始设置监听器:
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
同样地,监听器也是一个新概念,还是从代码入手:
// 这里的入参 type 是:org.springframework.context.ApplicationListener.class
private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type) {
return getSpringFactoriesInstances(type, new Class<?>[] {});
}
private <T> Collection<? extends 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<String>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
可以发现,这个加载相应的类名,然后完成实例化的过程和上面在设置初始化器时如出一辙,同样,还是以 spring-boot-autoconfigure 这个包中的 spring.factories 为例,看看相应的 Key-Value:
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer
至于 ApplicationListener 接口,它是 Spring 框架中一个相当基础的接口了,代码如下:
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
<span class="hljs-comment">/**
* Handle an application event.
* <span class="hljs-doctag">@param</span> event the event to respond to
*/</span>
<span class="hljs-keyword">void</span> <span class="hljs-title function_ invoke__">onApplicationEvent</span>(E event);
}
这个接口基于 JDK 中的 EventListener 接口,实现了观察者模式。对于 Spring 框架的观察者模式实现,它限定感兴趣的事件类型需要是 ApplicationEvent 类型的子类,而这个类同样是继承自 JDK 中的 EventObject 类。
2.1.4. 推断应用入口类
this.mainApplicationClass = deduceMainApplicationClass();
- 1
这个方法的实现有点意思:
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 实例的初始化过程就结束了。
2.2 SpringApplication.run 方法
完成了实例化,下面开始调用 run 方法:
// 运行 run 方法
public ConfigurableApplicationContext run(String... args) {
// 计时工具
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = <span class="hljs-literal">null</span>;
Collection<SpringBootExceptionReporter> exceptionReporters = <span class="hljs-keyword">new</span> ArrayList<>();
<span class="hljs-comment">// 设置java.awt.headless系统属性为true - 没有图形化界面</span>
configureHeadlessProperty();
<span class="hljs-comment">// KEY 1 - 获取SpringApplicationRunListeners</span>
SpringApplicationRunListeners listeners = getRunListeners(args);
<span class="hljs-comment">// 发出开始执行的事件</span>
listeners.starting();
<span class="hljs-keyword">try</span> {
ApplicationArguments applicationArguments = <span class="hljs-keyword">new</span> DefaultApplicationArguments(
args);
<span class="hljs-comment">// KEY 2 - 根据SpringApplicationRunListeners以及参数来准备环境</span>
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
configureIgnoreBeanInfo(environment);
<span class="hljs-comment">// 准备Banner打印器 - 就是启动Spring Boot的时候打印在console上的ASCII艺术字体</span>
Banner printedBanner = printBanner(environment);
<span class="hljs-comment">// KEY 3 - 创建Spring上下文</span>
context = createApplicationContext();
<span class="hljs-comment">// 准备异常报告器</span>
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.<span class="hljs-keyword">class</span>,
<span class="hljs-keyword">new</span> Class[] { ConfigurableApplicationContext.<span class="hljs-keyword">class</span> }, context);
<span class="hljs-comment">// KEY 4 - Spring上下文前置处理</span>
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
<span class="hljs-comment">// KEY 5 - Spring上下文刷新</span>
refreshContext(context);
<span class="hljs-comment">// KEY 6 - Spring上下文后置处理</span>
afterRefresh(context, applicationArguments);
<span class="hljs-comment">// 发出结束执行的事件</span>
listeners.finished(context, <span class="hljs-literal">null</span>);
<span class="hljs-comment">// 停止计时器</span>
stopWatch.stop();
<span class="hljs-keyword">if</span> (<span class="hljs-keyword">this</span>.logStartupInfo) {
<span class="hljs-keyword">new</span> StartupInfoLogger(<span class="hljs-keyword">this</span>.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
<span class="hljs-keyword">return</span> context;
}
<span class="hljs-keyword">catch</span> (Throwable ex) {
handleRunFailure(context, listeners, exceptionReporters, ex);
<span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> IllegalStateException(ex);
}
}
这个 run 方法包含的内容也是有点多的,根据上面列举出的关键步骤逐个进行分析:
2.2.1 第一步 - 获取所谓的 run listeners:
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
SpringApplicationRunListener.class, types, this, args));
}
这里仍然利用了 getSpringFactoriesInstances 方法来获取实例:
// 这里的入参:
// type: SpringApplicationRunListener.class
// parameterTypes: new Class<?>[] { SpringApplication.class, String[].class };
// args: SpringApplication 实例本身 + main 方法传入的 args
private <T> Collection<? extends 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<String>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
所以这里还是故技重施,从 META-INF/spring.factories 中读取 Key 为 org.springframework.boot.SpringApplicationRunListener 的 Values:
比如在 spring-boot 包中的定义的 spring.factories:
# Run Listeners
org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener
我们来看看这个 EventPublishingRunListener 是干嘛的:
/**
* {@link SpringApplicationRunListener} to publish {@link SpringApplicationEvent}s.
* <p>
* Uses an internal {@link ApplicationEventMulticaster} for the events that are fired
* before the context is actually refreshed.
*
* @author Phillip Webb
* @author Stephane Nicoll
*/
public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
// ...
}
从类文档可以看出,它主要是负责发布 SpringApplicationEvent 事件的,它会利用一个内部的 ApplicationEventMulticaster 在上下文实际被刷新之前对事件进行处理。至于具体的应用场景,后面用到的时候再来分析。
2.2.2 第二步 - 根据 SpringApplicationRunListeners 以及参数来准备环境
private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) {
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
listeners.environmentPrepared(environment);
if (!this.webEnvironment) {
environment = new EnvironmentConverter(getClassLoader())
.convertToStandardEnvironmentIfNecessary(environment);
}
return environment;
}
配置环境的方法:
protected void configureEnvironment(ConfigurableEnvironment environment,
String[] args) {
configurePropertySources(environment, args);
configureProfiles(environment, args);
}
所以这里实际上也包含了两个步骤:
- 配置 Property Sources
- 配置 Profiles
具体实现这里就不展开了,代码也比较直观。
对于 Web 应用而言,得到的 environment 变量是一个 StandardServletEnvironment 的实例。得到实例后,会调用前面 RunListeners 中的 environmentPrepared 方法:
@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(
this.application, this.args, environment));
}
在这里,定义的广播器就派上用场了,它会发布一个 ApplicationEnvironmentPreparedEvent 事件。
那么有发布就有监听,在构建 SpringApplication 实例的时候不是初始化过一些 ApplicationListeners 嘛,其中的 Listener 就可能会监听 ApplicationEnvironmentPreparedEvent 事件,然后进行相应处理。
所以这里 SpringApplicationRunListeners 的用途和目的也比较明显了,它实际上是一个事件中转器,它能够感知到 Spring Boot 启动过程中产生的事件,然后有选择性的将事件进行中转。为何是有选择性的,看看它的实现就知道了:
@Override
public void contextPrepared(ConfigurableApplicationContext context) {
}
它的 contextPrepared 方法实现为空,没有利用内部的 initialMulticaster 进行事件的派发。因此即便是外部有 ApplicationListener 对这个事件有兴趣,也是没有办法监听到的。
那么既然有事件的转发,是谁在监听这些事件呢,在这个类的构造器中交待了:
public EventPublishingRunListener(SpringApplication application, String[] args) {
this.application = application;
this.args = args;
this.initialMulticaster = new SimpleApplicationEventMulticaster();
for (ApplicationListener<?> listener : application.getListeners()) {
this.initialMulticaster.addApplicationListener(listener);
}
}
前面在构建 SpringApplication 实例过程中设置的监听器在这里被逐个添加到了 initialMulticaster 对应的 ApplicationListener 列表中。所以当 initialMulticaster 调用 multicastEvent 方法时,这些 Listeners 中定义的相应方法就会被触发了。
2.2.3 第三步 - 创建 Spring 上下文
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
contextClass = Class.forName(this.webEnvironment
? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS);
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Unable create a default ApplicationContext,"
+ "please specify an ApplicationContextClass",
ex);
}
}
return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass);
}
// WEB 应用的上下文类型
public static final String DEFAULT_WEB_CONTEXT_CLASS = "org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext";
这个上下文类型的类图如下所示:
这也是相当复杂的一个类图了,如果能把这张图中的各个类型的作用弄清楚,估计也是一个 Spring 大神了 :)
对于我们的 Web 应用,上下文类型就是 DEFAULT_WEB_CONTEXT_CLASS。
2.2.4 第四步 - Spring 上下文前置处理
private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
// 将环境和上下文关联起来
context.setEnvironment(environment);
<span class="hljs-comment">// 为上下文配置Bean生成器以及资源加载器(如果它们非空)</span>
postProcessApplicationContext(context);
<span class="hljs-comment">// 调用初始化器</span>
applyInitializers(context);
<span class="hljs-comment">// 触发Spring Boot启动过程的contextPrepared事件</span>
listeners.contextPrepared(context);
<span class="hljs-keyword">if</span> (<span class="hljs-keyword">this</span>.logStartupInfo) {
logStartupInfo(context.getParent() == <span class="hljs-literal">null</span>);
logStartupProfileInfo(context);
}
<span class="hljs-comment">// 添加两个Spring Boot中的特殊单例Beans - springApplicationArguments以及springBootBanner</span>
context.getBeanFactory().registerSingleton(<span class="hljs-string">"springApplicationArguments"</span>,
applicationArguments);
<span class="hljs-keyword">if</span> (printedBanner != <span class="hljs-literal">null</span>) {
context.getBeanFactory().registerSingleton(<span class="hljs-string">"springBootBanner"</span>, printedBanner);
}
<span class="hljs-comment">// 加载sources - 对于DemoApplication而言,这里的sources集合只包含了它一个class对象</span>
Set<Object> sources = getSources();
Assert.notEmpty(sources, <span class="hljs-string">"Sources must not be empty"</span>);
<span class="hljs-comment">// 加载动作 - 构造BeanDefinitionLoader并完成Bean定义的加载</span>
load(context, sources.toArray(<span class="hljs-keyword">new</span> Object[sources.size()]));
<span class="hljs-comment">// 触发Spring Boot启动过程的contextLoaded事件</span>
listeners.contextLoaded(context);
}
关键步骤:
配置 Bean 生成器以及资源加载器 (如果它们非空):
protected void postProcessApplicationContext(ConfigurableApplicationContext context) {
if (this.beanNameGenerator != null) {
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());
}
}
}
调用初始化器
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);
}
}
这里终于用到了在创建 SpringApplication 实例时设置的初始化器了,依次对它们进行遍历,并调用 initialize 方法。
2.2.5 第五步 - Spring 上下文刷新
private void refreshContext(ConfigurableApplicationContext context) {
// 由于这里需要调用父类一系列的 refresh 操作,涉及到了很多核心操作,因此耗时会比较长,本文不做具体展开
refresh(context);
<span class="hljs-comment">// 注册一个关闭容器时的钩子函数</span>
<span class="hljs-keyword">if</span> (<span class="hljs-keyword">this</span>.registerShutdownHook) {
<span class="hljs-keyword">try</span> {
context.registerShutdownHook();
}
<span class="hljs-keyword">catch</span> (AccessControlException ex) {
<span class="hljs-comment">// Not allowed in some environments.</span>
}
}
}
// 调用父类的 refresh 方法完成容器刷新的基础操作
protected void refresh(ApplicationContext applicationContext) {
Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
((AbstractApplicationContext)applicationContext).refresh();
}
注册关闭容器时的钩子函数的默认实现是在 AbstractApplicationContext 类中:
public void registerShutdownHook() {
if(this.shutdownHook == null) {
this.shutdownHook = new Thread() {
public void run() {
synchronized(AbstractApplicationContext.this.startupShutdownMonitor) {
AbstractApplicationContext.this.doClose();
}
}
};
Runtime.getRuntime().addShutdownHook(this.shutdownHook);
}
}
如果没有提供自定义的 shutdownHook,那么会生成一个默认的,并添加到 Runtime 中。默认行为就是调用它的 doClose 方法,完成一些容器销毁时的清理工作。
2.2.6 第六步 - Spring 上下文后置处理
protected void afterRefresh(ConfigurableApplicationContext context,
ApplicationArguments args) {
callRunners(context, args);
}
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List<Object> runners = new ArrayList<Object>();
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
AnnotationAwareOrderComparator.sort(runners);
for (Object runner : new LinkedHashSet<Object>(runners)) {
if (runner instanceof ApplicationRunner) {
callRunner((ApplicationRunner) runner, args);
}
if (runner instanceof CommandLineRunner) {
callRunner((CommandLineRunner) runner, args);
}
}
}
private void callRunner(ApplicationRunner runner, ApplicationArguments args) {
try {
(runner).run(args);
}
catch (Exception ex) {
throw new IllegalStateException("Failed to execute ApplicationRunner", ex);
}
}
private void callRunner(CommandLineRunner runner, ApplicationArguments args) {
try {
(runner).run(args.getSourceArgs());
}
catch (Exception ex) {
throw new IllegalStateException("Failed to execute CommandLineRunner", ex);
}
}
所谓的后置操作,就是在容器完成刷新后,依次调用注册的 Runners。Runners 可以是两个接口的实现类:
- org.springframework.boot.ApplicationRunner
- org.springframework.boot.CommandLineRunner
这两个接口有什么区别呢:
/**
* Interface used to indicate that a bean should <em>run</em> when it is contained within
* a {@link SpringApplication}. Multiple {@link ApplicationRunner} beans can be defined
* within the same application context and can be ordered using the {@link Ordered}
* interface or {@link Order @Order} annotation.
*
* @author Phillip Webb
* @since 1.3.0
* @see CommandLineRunner
*/
public interface ApplicationRunner {
<span class="hljs-comment">/**
* Callback used to run the bean.
* <span class="hljs-doctag">@param</span> args incoming application arguments
* <span class="hljs-doctag">@throws</span> Exception on error
*/</span>
<span class="hljs-keyword">void</span> <span class="hljs-title function_">run</span><span class="hljs-params">(ApplicationArguments args)</span> <span class="hljs-keyword">throws</span> Exception;
}
/**
-
Interface used to indicate that a bean should <em>run</em> when it is contained within
-
a {@link SpringApplication}. Multiple {@link CommandLineRunner} beans can be defined
-
within the same application context and can be ordered using the {@link Ordered}
-
interface or {@link Order @Order} annotation.
-
<p>
-
If you need access to {@link ApplicationArguments} instead of the raw String array
-
consider using {@link ApplicationRunner}.
-
@author Dave Syer
-
@see ApplicationRunner
*/
public interface CommandLineRunner {
/**
- Callback used to run the bean.
- @param args incoming main method arguments
- @throws Exception on error
*/
void run(String... args) throws Exception;
}
其实没有什么不同之处,除了接口中的 run 方法接受的参数类型是不一样的以外。一个是封装好的 ApplicationArguments 类型,另一个是直接的 String 不定长数组类型。因此根据需要选择相应的接口实现即可。
至此,SpringApplication 的 run 方法就分析完毕了。
3. 总结
本文分析了 Spring Boot 启动时的关键步骤,主要包含以下两个方面:
-
SpringApplication 实例的构建过程
其中主要涉及到了初始化器 (Initializer) 以及监听器 (Listener) 这两大概念,它们都通过 META-INF/spring.factories 完成定义。
-
SpringApplication 实例 run 方法的执行过程
其中主要有一个 SpringApplicationRunListeners 的概念,它作为 Spring Boot 容器初始化时各阶段事件的中转器,将事件派发给感兴趣的 Listeners(在 SpringApplication 实例的构建过程中得到的)。这些阶段性事件将容器的初始化过程给构造起来,提供了比较强大的可扩展性。
如果从可扩展性的角度出发,应用开发者可以在 Spring Boot 容器的启动阶段,扩展哪些内容呢:
- 初始化器 (Initializer)
- 监听器 (Listener)
- 容器刷新后置 Runners(ApplicationRunner 或者 CommandLineRunner 接口的实现类)
- 启动期间在 Console 打印 Banner 的具体实现类