本文采用知识共享 署名-相同方式共享 4.0 国际 许可协议进行许可。
访问 https://creativecommons.org/licenses/by-sa/4.0/ 查看该许可协议。
SpringBoot 启动流程
撰写本文时,源码参考上游最新版的 RELEASE 版 2.3.4.RELEASE, 那就直接从官方例子启动类开始挖啦。
1) SpringBoot 启动源码
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
@SpringBootApplication
注解我们暂时不提,下文中会重新讲到这个注解, SpringApplication.run
即 SpringBoot 启动的入口:
public class SpringApplication {
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);
}
}
我们沿着 run
方法向下走, 第一个 run
方法中用数组包装了一下启动类 Class, 第二个 run
方法中一共干了两件事儿, 不妨拆开来看:
new SpringApplication(primarySources)
创建 SpringApplication 实例的过程即 SpringBoot 初始化的过程, 在本文 1.1 会讲到.run(args)
此run
方法即 SpringBoot 真正的启动过程, 在本文 1.2 中讲到
1.1) SpringApplication
我们来看一下 new SpringApplication(primarySources)
的源码:
public class SpringApplication {
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));
this.webApplicationType = WebApplicationType.deduceFromClasspath();
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
}
清理掉非重要代码后, 大概做了以下几件事儿:
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
初始化this.primarySource
主要资源类集合, 放入启动类, 暂且不提稍微眼熟一下this.webApplicationType = WebApplicationType.deduceFromClasspath();
初始化this.webApplicationType
, 判断我们的启动程序类型, 参考 1.1.1setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
初始化this.initializers
初始化器集合, 从 spring.factories 获取, 参考 1.1.2setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
初始化this.listeners
监听器集合, 从 spring.factories 获取, 参考 1.1.3this.mainApplicationClass = deduceMainApplicationClass();
初始化this.mainApplicationClass
主应用 Class, 通过获取堆栈信息, 取调用链中最近的一个主函数类, 参考 1.1.4
1.1.1 SpringApplication.webApplicationType
初始化 this.webApplicationType
, 判断我们的启动程序类型
this.webApplicationType = WebApplicationType.deduceFromClasspath();
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";
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;
}
}
这段源码很简单:
- 如程序依赖 SpringWebflux 但不依赖 SpringMVC 和 JERSEY, 即返回
REACTIVE
响应式程序 - 如程序不依赖 Servlet, 即返回
NONE
非 WEB 程序 - 否则即返回
SERVLET
程序
一般我们的项目都为 SERVLET 程序
1.1.2) SpringApplication.initializers
初始化 this.initializers
初始化器集合, 从 spring.factories 获取
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
先来看 getSpringFactoriesInstances
方法:
public class SpringApplication {
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;
}
}
ClassLoader classLoader = getClassLoader();
获取类加载器, 不太重要这里不提-
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
传入 ApplicationContextInitializer.class, 从 spring.factories 获取初始化器集合, 具体流程如下public final class SpringFactoriesLoader { public static List
loadFactoryNames(Class> factoryType, @Nullable ClassLoader classLoader) { String factoryTypeName = factoryType.getName(); return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList()); } } -
核心方法为
loadSpringFactories
返回 Map 类型集合, 然后从中获取 key 为 ApplicationContextInitializer.class 的初始化器类全路径集合public final class SpringFactoriesLoader { public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories"; private static Map
> loadSpringFactories(@Nullable ClassLoader classLoader) { MultiValueMap result = cache.get(classLoader); if (result != null) { return result; } try { Enumeration urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION)); result = new LinkedMultiValueMap<>(); while (urls.hasMoreElements()) { URL url = urls.nextElement(); UrlResource resource = new UrlResource(url); Properties properties = PropertiesLoaderUtils.loadProperties(resource); for (Map.Entry, ?> entry : properties.entrySet()) { String factoryTypeName = ((String) entry.getKey()).trim(); for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) { result.add(factoryTypeName, factoryImplementationName.trim()); } } } cache.put(classLoader, result); return result; } catch (IOException ex) { throw new IllegalArgumentException("Unable to load factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex); } } } - 代码看着多, 但做的事儿很少, 大致如下:
- 尝试从缓存直接拿
- 拿到包含所有 spring.factories 路径的 urls
- 遍历每个 spring.factories 文件, 从中获取 key 为 ApplicationContextInitializer.class 的数据如下
org.springframework.context.ApplicationContextInitializer=\ org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\ org.springframework.boot.context.ContextIdApplicationContextInitializer,\ org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\ org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer,\ org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer
- 将 value split 一下与 key 一起放入 Map 集合中
- 缓存至内存让后续不再重复产生磁盘 IO
- 返回从 spring.factories 中取得的初始化器集合
-
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
创建初始化器实例, 上一步只是将初始化器类的全路径取得, 我们还需要反射创建一下实例:public final class SpringFactoriesLoader { private
List createSpringFactoriesInstances(Class type, Class>[] parameterTypes, ClassLoader classLoader, Object[] args, Set names) { List instances = new ArrayList<>(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; } } 源码也很简单, 只是反射一下创建实例, 放进集合返回
AnnotationAwareOrderComparator.sort(instances);
顾名思义, 对集合排序, 若初始化器上有@Order
注解则优先按照 order 数值排序, 从小到大, 这里不细讲了意义不大
最后调用 setInitializers
赋值到 initializers 中, 这里不贴代码了
初始化器部分就讲完啦, 总结:
- 从 spring.factories 中获取初始化器列表
- 创建初始化器实例
- 对初始化器通过
@Order
注解排序 - 赋值到
SpringApplication.initializers
中
1.1.3) SpringApplication.listeners
初始化 this.listeners
监听器集合, 从 spring.factories 获取
监听器的初始化和初始化器几乎一样, 可参考上一节 1.1.2
- 从 spring.factories 中获取初始化器列表
- 创建监听器实例
- 对监听器通过
@Order
注解排序 - 赋值到
SpringApplication.listeners
中
1.1.4) SpringApplication.mainApplicationClass
this.mainApplicationClass = deduceMainApplicationClass();
初始化 this.mainApplicationClass 主应用 Class, 通过获取堆栈信息, 取调用链中最近的一个主函数类
源码是一个很经典的获取方法调用链的例子:
public class SpringApplication {
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;
}
}
看代码应该能看懂, 看不懂走一下 debug 就明白啦这里不讲了
1.2) SpringApplication.run
我们来康一哈源码:
public class SpringApplication {
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
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;
}
}
先来简单了解一下每一行代码的作用:
StopWatch stopWatch = new StopWatch(); stopWatch.start();
开头的 StopWatch 相关的代码, 作用主要是记录程序启动的时间, 然后在程序基本启动完成后输出一下启动程序的耗时, 具体参考 21,23 行, 太简单这里不细讲ConfigurableApplicationContext context = null;
定义了一下 Spring 的 ApplicationContext, 未赋值Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
初始化异常报告器集合, 用于存放从 spring.factories 中初始化的的异常报告器, 下面还会提到configureHeadlessProperty();
设置系统参数java.awt.headless
用于模拟各种服务器缺少的输入输出设备, 这里也不细讲, 可以百度java headless
SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting();
从 spring.factories 初始化 SpringApplicationRunListeners, 它相当于 SpringBoot 监听器模型中, 广播器的一个包装工具类, 这里 start 了所有支持 ApplicationStartingEvent 事件的监听器, 参见 1.2.1ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
将命令行传参包装了一下, 方便取参
1.2.1) listeners.starting
先来看看 getRunListeners
这个方法:
public class SpringApplication {
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
return new SpringApplicationRunListeners(logger,
getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}
}
还是 getSpringFactoriesInstances
这个方法, 这里不重复提了, 从 spring.factories 读取 key 为 SpringApplicationRunListeners 的对象做初始化
, 在 spring-boot.jar 和 spring-boot-autoconfigure.jar 中的 spring.factories 中能找到的类只有: org.springframework.boot.context.event.EventPublishingRunListener
class SpringApplicationRunListeners {
private final Log log;
private final List<SpringApplicationRunListener> listeners;
SpringApplicationRunListeners(Log log, Collection<? extends SpringApplicationRunListener> listeners) {
this.log = log;
this.listeners = new ArrayList<>(listeners);
}
}
初始化后丢进成员变量, 啥事儿也没做, 那我们接着看看 listener.starting();
吧, 这个才是本节的核心方法:
class SpringApplicationRunListeners {
void starting() {
for (SpringApplicationRunListener listener : this.listeners) {
listener.starting();
}
}
}
上文嗦到 listeners 中只有一个 EventPublishingRunListener, 它是监听器模型中, 广播器的一个包装工具类, 可以优雅的广播相应的事件
public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
@Override
public void starting() {
this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
}
}
如这个 starting()
方法, 广播了一个 ApplicationStartingEvent
事件给监听器们, 我们直接康康这个广播方法:
public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {
@Override
public void multicastEvent(ApplicationEvent event) {
multicastEvent(event, resolveDefaultEventType(event));
}
@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
Executor executor = getTaskExecutor();
for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
if (executor != null) {
executor.execute(() -> invokeListener(listener, event));
}
else {
invokeListener(listener, event);
}
}
}
private ResolvableType resolveDefaultEventType(ApplicationEvent event) {
return ResolvableType.forInstance(event);
}
}
- 第一个
multicastEvent
方法中, 在广播事件前, 调用了resolveDefaultEventType
方法对事件 Pack 了一下, 这里不详细嗦了 - 获取一下任务执行器
- 用
getApplicationListeners
获取对事件感兴趣的监听器, 参见 1.2.1.1 - 如果任务执行器不为空, 则交给任务执行器线程运行监听器, 反之则在主线程运行
- 顺着 invoke 的方法往里走可以发现最后只是调用了监听器的
onApplicationEvent
方法listener.onApplicationEvent(event);
1.2.1.1) getApplicationListeners
这是 SpringBoot 监听器相关的挺重要的一个方法, 获取对事件感兴趣的监听器集合, 先来康康源码:
public abstract class AbstractApplicationEventMulticaster
implements ApplicationEventMulticaster, BeanClassLoaderAware, BeanFactoryAware {
protected Collection<ApplicationListener<?>> getApplicationListeners(
ApplicationEvent event, ResolvableType eventType) {
Object source = event.getSource();
Class<?> sourceType = (source != null ? source.getClass() : null);
ListenerCacheKey cacheKey = new ListenerCacheKey(eventType, sourceType);
// Quick check for existing entry on ConcurrentHashMap...
ListenerRetriever retriever = this.retrieverCache.get(cacheKey);
if (retriever != null) {
return retriever.getApplicationListeners();
}
if (this.beanClassLoader == null ||
(ClassUtils.isCacheSafe(event.getClass(), this.beanClassLoader) &&
(sourceType == null || ClassUtils.isCacheSafe(sourceType, this.beanClassLoader)))) {
// Fully synchronized building and caching of a ListenerRetriever
synchronized (this.retrievalMutex) {
retriever = this.retrieverCache.get(cacheKey);
if (retriever != null) {
return retriever.getApplicationListeners();
}
retriever = new ListenerRetriever(true);
Collection<ApplicationListener<?>> listeners =
retrieveApplicationListeners(eventType, sourceType, retriever);
this.retrieverCache.put(cacheKey, retriever);
return listeners;
}
}
else {
// No ListenerRetriever caching -> no synchronization necessary
return retrieveApplicationListeners(eventType, sourceType, null);
}
}
}
- 根据传入事件, 生成对应的 CacheKey 缓存 key
- 尝试用 CacheKey 缓存中拿 retriever 缓存的监听器列表, 如果之前有缓存则直接返回
- 校验一下事件, 然后进入同步代码块, 仔细康康可以看出这里用了双重校验锁, 单例里常见的那个
- 再尝试从缓存中取, 以免重复 Put
- 使用
retrieveApplicationListeners
函数获取对传入事件感兴趣的监听器列表, 代码太长就不贴了, 简单说一下实现- 遍历 SpringBoot 初始化过程加载的所有监听器, 大致可分为两类监听器,
SmartApplicationListener
和ApplicationListener
SmartApplicationListener
则调用其supportsEventType
方法判断是否支持此事件,ApplicationListener
则通过获取泛型来判断是否支持该事件- 将支持该事件的监听器集合返回
- 遍历 SpringBoot 初始化过程加载的所有监听器, 大致可分为两类监听器,
- Put 进缓存中