一、前言
上文聊了 SpringBoot如何加载并处理spring.factories文件中的信息。本文我们接着聊在构建SpringApplication的过程中都做了什么?加载spring.factories文件中的哪些信息?
注:Spring Boot版本:2.3.7
二、初始化SpringApplication
先看一个简单的SpringBoot 启动类:
@SpringBootApplication
public class StartApplication {
public static void main(String[] args) {
SpringApplication.run(StartApplication.class, args);
}
根据SpringApplication的实现,当前启动类StartApplication 的引导语句SpringApplication.run(StartApplication.class, args); 经过两层深入,会进入到new SpringApplication(primarySources).run(args) 。
这里需要Class类型的primarySources参数,下面我先看一下这个参数有什么实际意义?
1、primarySources参数
primarySources参数实际为Spring Boot应用上下文的Configuration Class。换言之,Spring Boot应用上下文的Configuration Class也不一定非得是引导类(启动类),我们可以把Configuration Class抽出来,使用@EnableAutoConfiguration注解标注,然后将引导类(启动类)中的primarySources参数设置为抽出来的Configuration Class 。
示例如下:
1> 自定义 Configuration Class:
@Configuration
@EnableAutoConfiguration
public class MySpringApplicationConfiguration {
}
2> 修改引导类(启动类):
- 引导类中不再需要添加
@SpringBootApplication 注解
public class StartApplication {
public static void main(String[] args) {
SpringApplication.run(MySpringApplicationConfiguration.class, args);
}
2、SpringApplication构造过程
已知SpringApplication#run(Class, String…)方法的执行会伴随SpringApplication对象的初始化,其构造函数为:SpringApplication(Class…) 具体操作包括:
- 首先初始化资源加载器,默认为null;断言判断主要资源类不能为null,否者报错。
- 然后将主资源类primarySources存储到SpringApplication对象Set类型的
primarySources 属性中。 - 推断当前 WEB 应用类型,一共有三种:NONE,SERVLET,REACTIVE;默认是SERVLET。
- 加载Spring应用上下文初始化器:从"META-INF/spring.factories"文件中读取
ApplicationContextInitializer 类的实例名称集合,然后进行Set去重、利用反射实例化对象,最后按照Order排序后,赋值到SpringApplication的List类型的initializers属性上,一共7个。 - 加载Spring应用事件监听器:从"META-INF/spring.factories"文件中读取
ApplicationListener 类的实例名称集合,然后进行Set去重、利用反射实例化对象,最后按照Order排序后,赋值到SpringApplication的List类型的listeners属性上,一共11个。 - 推断主入口应用类:通过当前调用栈的解析,获取Main方法所在类,并赋值给SpringApplication的
mainApplicationClass 属性。
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();
}
下面具体看一下是怎么推断Web应用类型的?怎么加载Spring应用上下文初始化器、怎么加载Spring应用事件监听器的?又是怎么推断应用引导类 的?
1)推断Web应用类型
this.webApplicationType = WebApplicationType.deduceFromClasspath();
因为Web应用类型可能在SpringApplication构造后及run方法之前,再通过setWebApplicatioinType(WebApplicationType) 方法调整;又在推断Web应用类型的过程中,由于当前Spring应用上下文尚未准备,所以采用检查当前ClassLoader下基准Class 的存在性来推断Web应用类型。
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;
}
}
....
}
WEB 应用类型,一共有三种:NONE,SERVLET,REACTIVE。
deduceFromClasspath() 方法利用ClassUtils.isPresent(String, ClassLoader)方法依次判断reactive.DispatcherHandler 、ConfigurableWebApplicationContext 、Servlet 、servlet.DispatcherServlet 的存在性组合情况,从而判断Web 引用类型,具体逻辑如下:
- 如果
DispatcherHandler 存在,并且DispatcherServlet 和ServletContainer 不存在时,即:Spring Boot仅依赖WebFlux时,Web应用类型为REACTIVE; - 如果
Servlet 和ConfigurableWebApplicationContext 不存在,则当前应用为非Web应用,即NONE。因为这两个API是Spring Web MVC必须的依赖。 - 当Spring WebFlux和Spring Web MVC同时存在时,Web应用类型依旧是SERVLET。
当deduceFromClasspath() 方法执行完毕后,SpringApplication的构造进入“加载Spring应用上下文初始化器、Spring事件监听器”过程。
2)加载Spring应用上下文初始化器ApplicationContextInitializer
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
这个过程包括两个动作:
1> getSpringFactoriesInstances(ApplicationContextInitializer.class) 从"META-INF/spring.factories"文件中读取ApplicationContextInitializer 类的实例名称集合,然后进行Set去重、利用反射实例化对象,最后按照Order排序。 2> setInitializers(Collection) 将Collection赋值到SpringApplication的List类型的initializers属性上,一共7个。
getSpringFactoriesInstances(Class)在前一篇博文中详细聊过,移步:《SpringBoot启动流程一》:万字debug梳理SpringBoot如何加载并处理META-INF/spring.factories文件中的信息。
第二步单纯的赋值:
public void setInitializers(Collection<? extends ApplicationContextInitializer<?>> initializers) {
this.initializers = new ArrayList<>(initializers);
}
3)加载Spring事件应用监听器ApplicationListener
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
这个过程和加载Spring应用上下文初始化器ApplicationContextInitializer一样,包括两个动作。
第二步 setListeners(Collection) 将Collection赋值到SpringApplication的List类型的listeners属性上,一共11个。
public void setListeners(Collection<? extends ApplicationListener<?>> listeners) {
this.listeners = new ArrayList<>(listeners);
}
加载结果如下:
4)推断应用引导类
this.mainApplicationClass = deduceMainApplicationClass();
推断应用引导类是SpringApplication构建过程的最后一步,其执行方法deduceMainApplicationClass() :
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) {
}
return null;
}
该方法根据当前线程执行栈来判断其栈中哪个类包含main方法,然后将找到的类名通过反射返回Class对象。
至此,在SpringApplication构造过程中,SpringApplication属性primarySources、webApplicationType、initializers、listeners 和 mainApplicationClass都被初始化了,下一篇文博文继续聊SpringApplication的准备阶段。即:从run(String…)方法调用开始 到 refreshContext(ConfigurableApplicationContext调用前)
|