Spring Boot构造流程浅析

@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下是否存在指定类,从而判断出应用类型。推断逻辑如下:

  1. 如果类路径中包含DispatcherHandler且不包含DispatcherServlet,也不包含ServletContainer,则为Reactive应用。

  2. 如果类路径下Servlet或者ConfigurableWebApplicationContext任何一个不存在,则为非Web应用。

  3. 其他情况则为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);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值