Spring Boot构造流程浅析

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(type, parameterTypes,

classLoader, args, names);

//排序

AnnotationAwareOrderComparator.sort(instances);

return instances;

}

可以看到这里是通过SpringFactoriesLoader.loadFactoryNames()方法来加载META-INF/spring.factories文件中的对应配置,该文件的内容如下:

看到这里大家可能会觉得似曾相识,没错,这里加载META-INF/spring.factories文件的过程在之前讲解Spring Boot自动配置时已经提到过,spring.factories文件中的内容会被解析到Map<String,List>中,最后loadFactoryNames通过传递过来的class名称作为Key从Map中获得该类的配置列表,而这个class名称就是type的值,追溯type的值发现其实就是一开始传入的ApplicationContextInitializer.class。

上面通过SpringFactoriesLoader.loadFactoryNames()方法获取到了ApplicationContextInitializer接口具体的实现类的全限定名,下面就要调用createSpringFactoriesInstances()方法来创建这些实例,再将这些实例经过排序后返回,至此获取相关实例结束,下一步是设置实例。

下面看设置实例的方法:setInitializers()

public void setInitializers(

Collection<? extends ApplicationContextInitializer<?>> initializers) {

this.initializers = new ArrayList<>();

this.initializers.addAll(initializers);

}

可以看到设置实例的步骤很简单,将SpringApplication的initializers成员变量实例化为一个新的List,然后将刚才获取到的实例放入其中即可。至此加载并初始化ApplicationContextInitializer及相关实现类结束。

四、加载并初始化ApplicationListener及相关实现类

ApplicationListener经常用于监听容器初始化完成之后,执行数据加载、初始化缓存等任务。

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

毕竟工作也这么久了 ,除了途虎一轮,也七七八八面试了不少大厂,像阿里、饿了么、美团、滴滴这些面试过程就不一一写在这篇文章上了。我会整理一份详细的面试过程及大家想知道的一些问题细节

美团面试经验

美团面试
字节面试经验
字节面试
菜鸟面试经验
菜鸟面试
蚂蚁金服面试经验
蚂蚁金服
唯品会面试经验
唯品会

因篇幅有限,图文无法详细发出
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
0721)]
字节面试经验
[外链图片转存中…(img-5v6k91X7-1713297410721)]
菜鸟面试经验
[外链图片转存中…(img-KHbUTcst-1713297410722)]
蚂蚁金服面试经验
[外链图片转存中…(img-tpxbMLct-1713297410722)]
唯品会面试经验
[外链图片转存中…(img-w7DJesD6-1713297410722)]

因篇幅有限,图文无法详细发出
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值