@SpringBootApplication
public class UserApplication {
public static void main(String[] args) {
SpringApplication.run(UserApplication.class);
}
}
可以看到main方法中只有一句代码:SpringApplication.run(xxxx.class),我们进入这个run方法,如下:
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);
}
仔细看这句代码:new SpringApplication(primarySources).run(args),发现居然new了一个SpringApplication去调用另外一个run方法,其实这句代码包含了两个非常重要的内容,即Spring Boot的构造流程和运行流程,构造流程是指SpringApplication类的实例化过程,运行流程是指SpringApplication类的实例化对象调用run方法完成整个项目的初始化和启动的过程,而本文的重点是前者。
到这一步,我们基本能够明白一件事:入口类中主要通过SpringApplication的run方法进行SpringApplication类的实例化操作,然后这个实例化对象再去调用另外一个更牛逼的run方法来完成整个项目的初始化和启动。
下面我们将正式进入SpringApplication类的实例化过程的探析,首先看一下SpringApplication两个构造方法的源码:
public SpringApplication(Class<?>… primarySources) {
this(null, primarySources);
}
public SpringApplication(ResourceLoader resourceLoader, Class<?>… primarySources) {
//赋值成员变量resourceLoader
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, “PrimarySources must not be null”);
//赋值成员变量primarySources
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//推断Web应用类型
this.webApplicationType = WebApplicationType.deduceFromClasspath();
//加载并初始化ApplicationContextInitializer及相关实现类
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
//加载并初始化ApplicationListener及相关实现类
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//推断main方法
this.mainApplicationClass = deduceMainApplicationClass();
}
第一个构造方法其实是直接调用了第二个核心构造方法,核心业务逻辑便在其中,下面将详细讲解核心构造方法涉及到的业务逻辑。
一、赋值成员变量:resourceLoader、primarySources
可以看到核心构造方法包含两个参数:ResourceLoader和Class<?>…primarySources。其中前者为资源加载的接口,在Spring Boot启动时可以通过它来指定需要加载的文件路径;后者默认传入的是Spring Boot入口类,作为项目的引导类。构造方法的第一个步骤非常简单,就是将传进来的这两个参数赋值给对应的成员变量。
二、推断Web应用类型
接着是调用了WebApplicationType的deduceFromClasspath方法来推断Web应用类型,我们首先进入WebApplicationType类:
public enum WebApplicationType {
//非Web应用类型
NONE,
//基于Servlet的Web应用类型
SERVLET,
//基于Reactive的Web应用类型
REACTIVE;
…
}
可以知道WebApplicationType类只是一个枚举类型,包括:非Web应用类型,基于Servlet的Web应用类型,基于Reactive的Web应用类型。另外可以在WebApplicationType类中看到刚才提到的推断方法deduceFromClasspath(),推断方法以及用于推断的常量源码如下。
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”;
private static final String SERVLET_APPLICATION_CONTEXT_CLASS = “org.springframework.web.context.WebApplicationContext”;
private static final String REACTIVE_APPLICATION_CONTEXT_CLASS = “org.springframework.boot.web.reactive.context.ReactiveWebApplicationContext”;
static WebApplicationType deduceFromClasspath() {
//如果类路径中包含DispatcherHandler且不包含DispatcherServlet,也不包含ServletContainer,则为Reactive应用
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) {
//如果类路径下Servlet或者ConfigurableWebApplicationContext任何一个不存在,则为非Web应用
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
//否则都是Servlet应用类型
return WebApplicationType.SERVLET;
}
isPresent方法可以通过反射机制创建出指定类,根据在创建过程中是否抛出异常来判断指定类是否存在。分析该推断方法可以得知核心逻辑是通过ClassUtils.isPresent()来判断类路径classpath下是否存在指定类,从而判断出应用类型。推断逻辑如下:
-
如果类路径中包含DispatcherHandler且不包含DispatcherServlet,也不包含ServletContainer,则为Reactive应用。
-
如果类路径下Servlet或者ConfigurableWebApplicationContext任何一个不存在,则为非Web应用。
-
其他情况则为Servlet应用类型。
三、加载并初始化ApplicationContextInitializer及相关实现类
ApplicationContextInitializer的作用:它是Spring IOC容器提供的一个回调接口,通常用于应用程序上下文进行编程初始化的Web应用程序中。
在完成Web应用类型推断之后,接着便开始ApplicationContextInitializer的加载工作,这里将分成两个步骤:即获取相关实例和设置实例。对应的方法为:getSpringFactoriesInstances()和setInitializers()。我们首先来看getSpringFactoriesInstances()方法,源码如下:
private Collection getSpringFactoriesInstances(Class type) {
return getSpringFactoriesInstances(type, new Class<?>[] {});
}
private Collection getSpringFactoriesInstances(Class type,
Class<?>[] parameterTypes, Object… args) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// Use names and ensure unique to protect against duplicates
//加载META-INF/spring.factories文件中的对应配置
Set names = new LinkedHashSet<>(
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
//创建实例
List instances = createSpringFactoriesInstances(ty 《一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》无偿开源 威信搜索公众号【编程进阶路】 pe, parameterTypes,
classLoader, args, names);