注:本系列源码分析基于springboot 2.2.2.RELEASE,对应的spring版本为5.2.2.RELEASE,源码的gitee仓库仓库地址:gitee.com/funcy/sprin….
本文来探究springboot是如何启动的,本文使用到的demo位于gitee/funcy.
1. 从Demo01Application#main(...)开始
springboot的启动方式非常简单,就一行:
@SpringBootApplication
public class Demo01Application {
public static void main(String[] args) {
// 这一行就是用来启动springboot的
SpringApplication.run(Demo01Application.class, args);
}
}
然后我们就进入这个方法,看看它干了什么:
public class SpringApplication {
...
// primarySource 就是我们传入的 Demo01Application.class,
// args 就是 main() 方法的参数
public static ConfigurableApplicationContext run(
Class<?> primarySource, String... args) {
// 将 primarySource 包装成数组,继续调用 run(...) 方法
return run(new Class<?>[] { primarySource }, args);
}
// primarySources 就是我们传入的 Demo01Application.class 包装成的数组,
// args 就是 main() 方法的参数
public static ConfigurableApplicationContext run(
Class<?>[] primarySources, String[] args) {
// 这里开始干正事了
return new SpringApplication(primarySources).run(args);
}
...
}
通过方法一步步追查下去后,最后到了SpringApplication#run(Class<?>[], String[])方法,关键代码如下:
return new SpringApplication(primarySources).run(args);
这块代码需要拆开来看,可以拆成以下两部分:
- 构造方法:
SpringApplication#SpringApplication(Class<?>...) - 实例方法:
SpringApplication#run(String...)
看来,这两个方法就是 springboot 的启动所有流程了,接下来我们就来分析这两个方法。
2. 创建SpringApplication:SpringApplication#SpringApplication(Class<?>...)
public class SpringApplication {
public SpringApplication(Class<?>... primarySources) {
// 继续调用
this(null, primarySources);
}
/**
* 这里就最终调用的构造方法了
* resourceLoader 为 null
* primarySources 为 Demo01Application.class
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
// 1. 将传入的resourceLoader设置到成员变量,这里的值为null
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
// 2. 将传入的primarySources设置到成员变量,这里的值为 Demo01Application.class
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 3. 当前的 web 应用类型,REACTIVE,NONE,SERVLET
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 4. 设置初始化器,getSpringFactoriesInstances:从 META-INF/spring.factories 中获取配置
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 5. 设置监听器,getSpringFactoriesInstances:从 META-INF/spring.factories 中获取配置
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 6. 返回包含main()方法的class
this.mainApplicationClass = deduceMainApplicationClass();
}
}
这个方法所做的事还是比较清晰的,相关内容都已在代码中注释了,不过有些方法还需要展开,接下来我们就来看看。
2.1 获取当前 web 应用类型:WebApplicationType.deduceFromClasspath()
WebApplicationType.deduceFromClasspath() 方法是用来推断当前项目是什么类型的,代码如下:
public enum WebApplicationType {
// 不是 web 应用
NONE,
// servlet 类型的 web 应用
SERVLET,
// reactive 类型的 web 应用
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() {
// classpath 中仅存在 WEBFLUX 相关类
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
// classpath 不存在 SERVLET 相关类
for (String className : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
// 默认 web 类型为 SERVLET
// 也就是说,同时存在 WEBFLUX 与 SERVLET 相关类,最终返回的是 SERVLET
return WebApplicationType.SERVLET;
}
...
}
可以看到,springboot定义了三种项目类型:NONE(不是web应用)、SERVLET(servlet类型的web应用)、REACTIVE(reactive类型的web应用),WebApplicationType.deduceFromClasspath()的执行流程如下:
- 如果
classpath中仅存在WEBFLUX相关类,则表明当前项目是reactive类型的web应用,返回; - 如果
classpath中不存在SERVLET相关类,则表明当前项目不是web应用,返回; - 如果以上条件都不满足,则表明当前项目是
servlet类型的web应用。
由于demo引用了spring-boot-starter-web相关依赖,因此当前项目是servlet类型的web应用。
2.2 设置初始化器:setInitializers(...)
这块代码如下:
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
这行代码分为两部分:
- 获取
ApplicationContextInitializer:getSpringFactoriesInstances(ApplicationContextInitializer.class) - 设置初始化器:
setInitializers(...)
我们先来看看获取ApplicationContextInitializer的流程,代码如下:
public class SpringApplication {
...
// type 为 ApplicationContextInitializer.class
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
return getSpringFactoriesInstances(type, new Class<?>[] {});
}
/**
* type 为 ApplicationContextInitializer.class
* parameterTypes 为 ew Class<?>[] {}
* args 为 null
*/
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
// 从 META-INF/spring.factories 加载内容
Set<String> names = new LinkedHashSet<>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// 实例化,使用的反射操作
List<T> instances = createSpringFactoriesInstances(
type, parameterTypes, classLoader, args, names);
// 排序,比较的是 @Order 注解,或实现的 Orderd 接口
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
...
}
以上代码比较简单,先从 META-INF/spring.factories 获取内容,然后使用反射进行实例化,进行排序后再返回。
关于SpringFactoriesLoader.loadFactoryNames(...),它最终会从META-INF/spring.factories 加载内容,META-INF/spring.factories 可以理解为一个配置文件,key 为传入的type,该方法的详细分析,可以参考springboot 自动装配之加载自动装配类。
最终会有多少个 ApplicationContextInitializer 加载进来呢?通过调试,发现一共有7个:
对这7个ApplicationContextInitializer,说明如下:
ConfigurationWarningsApplicationContextInitializer:报告IOC容器的一些常见的错误配置ContextIdApplicationContextInitializer:设置Spring应用上下文的IDDelegatingApplicationContextInitializer:加载application.properties中context.initializer.classes配置的类RSocketPortInfoApplicationContextInitializer:将RSocketServer实际使用的监听端口写入到Environment环境属性中ServerPortInfoApplicationContextInitializer:将内置servlet容器实际使用的监听端口写入到Environment环境属性中SharedMetadataReaderFactoryContextInitializer:创建一个SpringBoot和ConfigurationClassPostProcessor共用的CachingMetadataReaderFactory对象ConditionEvaluationReportLoggingListener:将ConditionEvaluationReport写入日志
获取到ApplicationContextInitializer,我们再来看看setInitializers(...)方法:
public class SpringApplication {
...
public void setInitializers(
Collection<? extends ApplicationContextInitializer<?>> initializers) {
this.initializers = new ArrayList<>(initializers);
}
...
}
这是一个标准的setter方法,所做的就只是设置成员变量。
2.3 设置监听器:setListeners(...)
设置监听器的代码如下:
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
从形式上看,同Initializer一样,也是先从META-INF/spring.factories中加载ApplicationListener,然后添加到成员变量中,这里我们直接看能获取到哪些listener:
可以看到,一共可以获取到11个listener,这些listener的作用如下:
ClearCachesApplicationListener:应用上下文加载完成后对缓存做清除工作ParentContextCloserApplicationListener:监听双亲应用上下文的关闭事件并往自己的子应用上下文中传播CloudFoundryVcapEnvironmentPostProcessor:对CloudFoundry提供支持FileEncodingApplicationListener:检测系统文件编码与应用环境编码是否一致,如果系统文件编码和应用环境的编码不同则终止应用启动AnsiOutputApplicationListener:根据spring.output.ansi.enabled参数配置AnsiOutputConfigFileApplicationListener:从常见的那些约定的位置读取配置文件DelegatingApplicationListener:监听到事件后转发给application.properties中配置的context.listener.classes的监听器ClasspathLoggingApplicationListener:对环境就绪事件ApplicationEnvironmentPreparedEvent和应用失败事件ApplicationFailedEvent做出响应LoggingApplicationListener:配置LoggingSystem,使用logging.config环境变量指定的配置或者缺省配置LiquibaseServiceLocatorApplicationListener:使用一个可以和SpringBoot可执行jar包配合工作的版本替换LiquibaseServiceLocatorBackgroundPreinitializer:使用一个后台线程尽早触发一些耗时的初始化任务
再来看看SpringApplication#setListeners:
public void setListeners(Collection<? extends ApplicationListener<?>> listeners) {
this.listeners = new ArrayList<>(listeners);
}
这也是一个标准的setter方法。
2.4 推断主类:deduceMainApplicationClass()
所谓主类,就是包含main(String[]),也就是当前spring应用的启动类,SpringApplication#deduceMainApplicationClass 代码如下:
private Class<?> deduceMainApplicationClass() {
try {
// 获取调用栈
StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
// 遍历调用栈,找 到main方法
for (StackTraceElement stackTraceElement : stackTrace) {
if ("main".equals(stackTraceElement.getMethodName())) {
return Class.forName(stackTraceElement.getClassName());
}
}
}
catch (ClassNotFoundException ex) {
// Swallow and continue
}
return null;
}
这里主要是通过new RuntimeException().getStackTrace()获取调用栈,然后遍历,得到包含main方法的类,得到的调用栈如下:
可以看到,main() 就包含在调用栈中了。
2.5 总结
本文主要是介绍SpringApplication的创建过程,我们重点分析了以下几点:
- 推断当前web应用类型:NONE, SERVLET,REACTIVE;
- 设置初始化器:
ApplicationContextInitializer; - 设置监听器:
ApplicationListener; - 推断主类。
本文原文链接:my.oschina.net/funcy/blog/… ,限于作者个人水平,文中难免有错误之处,欢迎指正!原创不易,商业转载请联系作者获得授权,非商业转载请注明出处。