SpringBoot启动流程
前言
为什么要学习框架的启动流程?
因为这不仅是面试中常见的考点,而且掌握一个框架的启动流程,可以让你在运用它进行开发时游刃有余。甚至可以说,学习一个框架的起步,都是从学习框架的启动开始的。
下面会从源码角度对启动流程做一个剖析,建议读者可以跟着文章内容进行调试,那样可以理解的更透彻。
源码版本为 spring-boot-2.2.1.RELEASE.jar
启动流程
初始化SpringApplication对象
- 获取所有的ApplicationContextInitializer接口的实现类
- 获取所有的ApplicationListener接口的实现类
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();
// 获取所有的ApplicationContextInitializer接口的实现类
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 获取所有的ApplicationListener接口的实现类
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
如何获取到实现类的呢?主要通过 getSpringFactoriesInstances 方法
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);
// Spring内置排序器对实例进行排序
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
那 SpringFactoriesLoader 的 loadFactoryNames 方法如何工作
public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
String factoryTypeName = factoryType.getName();
// 返回 factoryType 下的实现类名列表
return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());
}
// 获取类加载器下 META-INF/spring.factories 资源并解析
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = cache.get(classLoader);
// 如果缓存中存在则直接返回
if (result != null) {
return result;
}
// FACTORIES_RESOURCE_LOCATION="META-INF/spring.factories"
// 只要外部依赖中存在 spring.factories 文件配置,那么就会将文件绝对路径加载到 urls
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
result = new LinkedMultiValueMap<>();
// 解析每个 spring.factories 文件
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;
}
}
spring.factories 内容示例:
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer
SpringApplication对象调用run方法
(1)构造 SpringApplicationRunListeners,用来监听启动过程中的事件并回调。
SpringApplicationRunListeners 会存储 SpringApplicationRunListener 的实现类实例,并构成链表结构,默认只有一个 EventPublishRunListener,且优先级最高,位于链表首部。
获取所有 Listener 还是通过 getSpringFactoriesInstances 方法
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };
return new SpringApplicationRunListeners(logger,
getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));
}
SpringApplicationRunListener 调用 starting 方法
class SpringApplicationRunListeners {
private final List<SpringApplicationRunListener> listeners;
void starting() {
for (SpringApplicationRunListener listener : this.listeners) {
listener.starting();
}
}
}
SpringApplicationRunListener 回调匹配类型的 ApplicationListener 的 onApplicationEvent 方法(使用见 spring自定义listener#自定义ApplicationListener)
@Override
public void starting() {
// 广播事件
this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));
}
@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();
// getApplicationListeners 获取匹配 type 类型的听众
for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
if (executor != null) {
executor.execute(() -> invokeListener(listener, event));
} else {
invokeListener(listener, event);
}
}
}
(2)封装命令行参数,创建 Environment,回调 environmentPrepared 事件,并打印横幅(banner)
Environment 就是 Spring 环境的抽象,包括 profile 和 properties 两个方面。Environment 对于 profile 来说,可以在不同环境下注册不同的 bean;对于 properties 来说,提供解析和读取属性配置的工具。
// 封装参数到 ApplicationArguments
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// listeners 回调 environmentPrepared 事件
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
// 打印横幅
Banner printedBanner = printBanner(environment);
(3)加载上下文
实例化 ApplicationContext,需要判断创建 service web context, 还是新出现的 reactive web context, 或者默认的 context
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);
}
预处理上下文
// 将环境变量写入上下文
context.setEnvironment(environment);
// 回调 ApplicationContextInitializer 的 initialize 方法
applyInitializers(context);
// 回调 Listener 的 contextPrepared 和 contextLoaded
listeners.contextPrepared(context);
listeners.contextLoaded(context);
刷新上下文
- 完成 ioc 容器构建,如果是 web 应用会创建内嵌的 Tomcat
- 扫描,加载外部组件自动配置信息(自动配置 @EnableAutoConfiguration)
- 回调 Listener 的 started
- 从ioc容器中获取所有的 ApplicationRunner 和 CommandLineRunner 进行回调。ApplicationRunner 先回调,CommandLineRunner 再回调
AutoConfigurationImportSelector#getAutoConfigurationEntry 加载自动配置信息
AnnotationAttributes attributes = getAttributes(annotationMetadata);
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
// 过滤掉条件不满足的自动配置类(ConditionalOn)
configurations = filter(configurations, autoConfigurationMetadata);
总结:
spring 调用 run 方法,其实就是在调用 Event 来触发 SpringApplicationRunListener 接口的生命周期函数,通过回调相应函数来完成 spring 前置配置、bean初始化、tomcat 初始化等等。
spring 每个 event 的执行动作见 spring event执行动作
列举比较重要的 event:
ApplicationContextInitializedEvent
,context 初始化完成,但还没有初始化 bean;
ApplicationPreparedEvent
,context 刷新之前,加载 BeanDefinition,依然还没有初始化 bean;
ApplicationStartedEvent
,在 context 刷新之后执行,此时 spring 容器已经初始化完成,并且 bean 已经正确实例化;
ApplicationReadyEvent
,spring 容器初始化完成后,接下来就是加载中间件,比如 Tomcat,Tomcat 启动完成后回调该事件。