Spring Boot 源码学习系列

在这里插入图片描述

引言

往期的博文Huazie 围绕 Spring Boot核心功能,带大家从总整体上了解 Spring Boot 自动配置原理以及自动配置核心组件的运作过程。这些内容大家需要重点关注,只有了解这些基础的组件和功能我们在后续集成其他三方类库的 Starters 时,才能够更加清晰地了解它们都运用了自动配置哪些功能

学习上述 Spring Boot 核心功能过程中,相信大家可能都会尝试启动自己新建Spring Boot项目,并 Debug 看看具体的执行过程本篇开始就将从 Spring Boot启动SpringApplication 上入手,带领大家了解 Spring Boot 启动过程中所涉及到的源码知识点

在这里插入图片描述

往期内容

在开始本篇内容介绍之前,我们先来看看往期的系列文章【有需要的朋友,欢迎关注系列专栏】:

Spring Boot 源码学习
Spring Boot 项目介绍
Spring Boot 核心运行原理介绍
【Spring Boot 源码学习】@EnableAutoConfiguration 注解
【Spring Boot 源码学习】@SpringBootApplication 注解
【Spring Boot 源码学习】走近 AutoConfigurationImportSelector
【Spring Boot 源码学习】自动装配流程源码解析(上)
【Spring Boot 源码学习】自动装配流程源码解析(下)
【Spring Boot 源码学习】深入 FilteringSpringBootCondition
【Spring Boot 源码学习】OnClassCondition 详解
【Spring Boot 源码学习】OnBeanCondition 详解
【Spring Boot 源码学习】OnWebApplicationCondition 详解
【Spring Boot 源码学习】@Conditional 条件注解
【Spring Boot 源码学习】HttpEncodingAutoConfiguration 详解
【Spring Boot 源码学习】RedisAutoConfiguration 详解
【Spring Boot 源码学习】JedisConnectionConfiguration 详解

主要内容

1. Spring Boot 应用程序的启动

在 《【Spring Boot 源码学习】@SpringBootApplication 注解》这篇博文中,我们新建一个基于 Spring Boot测试项目

在这里插入图片描述

如上图中的 DemoApplication 就是我们这里 Spring Boot 项目的入口类。

同时,我们可以看到 DemoApplicationmain 方法中,直接调用SpringApplication静态方法 run用于启动整个 Spring Boot 项目

先来看看 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);
}

阅读上述 run 方法,我们可以看到实际上是 new一个SpringApplication 对象【其构造参数 primarySources加载的主要资源类,通常就是 SpringBoot 的入口类】,并调用run 方法【其参数 args传递应用程序参数信息】启动,然后返回一个应用上下文对象 ConfigurableApplicationContext

通过观察这个内部run 方法实现,我们也可以自己Spring Boot 启动入口类中,像如下这样去写 :

@SpringBootApplication
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication springApplication = new SpringApplication(DemoApplication.class);
        // 这里可以调用 SpringApplication 提供的 setXX 或 addXX 方法来定制设置
        springApplication.run(args);
    }

}

2. SpringApplication 的实例

上面已经看到我们在实例SpringApplication 了,废话不多说,直接翻看其源码【Spring Boot 2.7.9】:

	public SpringApplication(Class<?>... primarySources) {
		this(null, primarySources);
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
		this.resourceLoader = resourceLoader;
		Assert.notNull(primarySources, "PrimarySources must not be null");
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
		// 推断web应用类型
		this.webApplicationType = WebApplicationType.deduceFromClasspath();
		// 加载初始化 BootstrapRegistryInitializer及其实现
		this.bootstrapRegistryInitializers = new ArrayList<>(
				getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
		// 加载初始化 ApplicationContextInitializer及其实现类
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
		// 加载并初始化ApplicationListener及其实现类
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		// 推断入口类
		this.mainApplicationClass = deduceMainApplicationClass();
	}

由上可知,SpringApplication 提供了两个构造方法,而其核心逻辑都在第二个构造方法中实现。

2.1 构造方法参数

我们从上述源码可知,SpringApplication第二个构造方法两个参数,分别是:

有些朋友,可能对 primarySources 这个可变参数的描述有点疑惑,下面我们就用实例来演示以其他引导类为入口类进行 Spring Boot 项目启动:

翻看 SpringApplication源码,我们在其中还能看到它提供了追加 primarySources 的方法,如下所示

public void addPrimarySources(Collection<Class<?>> additionalPrimarySources) {
	this.primarySources.addAll(additionalPrimarySources);
}

如果采用 1 中最后方式启动 Spring Boot ,我们就可以调用 addPrimarySources 方法来追加额外primarySources

我们继续回到 SpringApplication构造方法里,可以看到如下代码

this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));

上述这里primarySources 参数转换LinkedHashSet 集合,并赋值SpringApplication私有成员变量 Set<Class<?>> primarySources

知识点 LinkedHashSetJava 集合框架中的类,它继承HashSet,因此具有哈希表的查找性能。这是一个同时使用链表哈希特性数据结构,其中链表用于维护元素插入顺序。也即是说,当你向 LinkedHashSet 添加元素时,元素将按照添加顺序存储,并且能够被遍历输出
此外,LinkedHashSet 还确保了 元素唯一性,即重复元素集合中只会存在一份
如果需要频繁遍历集合,那么 LinkedHashSet 可能会比 HashSet 效率更高,因为其通过维护一个双向链表记录元素添加顺序,从而支持按照插入顺序排序迭代。但需要注意的是,LinkedHashSet 是非线程安全的,如果有多个线程同时访问集合容器,可能会引发并发问题

2.2 Web 应用类型推断

我们继续往下翻看源码这里调用了 WebApplicationTypededuceFromClasspath 方法来进行 Web 应用类型推断

this.webApplicationType = WebApplicationType.deduceFromClasspath();

我们继续翻看 WebApplicationType源码

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() {
		if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) &amp;&amp; !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
				&amp;&amp; !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;
	}
}

WebApplicationType 是一个定义了可能的Web应用类型的枚举类,该枚举类中包含了三块逻辑

在上述的 deduceFromClasspath 方法中,我们可以看到,在判断的过程中使用到ClassUtilsisPresent 方法。该工具类方法就是通过反射创建指定的类,根据在创建过程中是否抛出异常判断该类是否存在

在这里插入图片描述

2.3 加载 BootstrapRegistryInitializer

this.bootstrapRegistryInitializers = new ArrayList<>(
				getSpringFactoriesInstances(BootstrapRegistryInitializer.class));

上述逻辑用于加载并初始化 BootstrapRegistryInitializer 及其相关的类。

BootstrapRegistryInitializerSpring Cloud Config 的组件之一,它的作用是在应用程序启动时初始化 Spring Cloud Config 客户端

Spring Cloud Config 中,客户端通过配置中心Config Server发送请求获取应用程序的配置信息。而 BootstrapRegistryInitializer 就是负责将配置中心相关信息注册Spring器中的。

由于篇幅有限,有关 BootstrapRegistryInitializer 更详细的内容,笔者后续专门讲解

2.4 加载 ApplicationContextInitializer

setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));

上述代码用于加载并初始化 ApplicationContextInitializer 及其相关的类。

ApplicationContextInitializerSpring 框架中的一个接口,它的主要作用是在Spring容器刷新之前初始化 ConfigurableApplicationContext。这个接口的实现类可以被视为回调函数,它们的 onApplicationEvent 方法会在Spring 容器启动时被自动调用,从而允许开发人员容器刷新之前执行一些自定义操作

例如,我们可能需要在这个时刻加载一些配置信息,或者对某些 bean 进行预处理等。通过实现 ApplicationContextInitializer 接口重写onApplicationEvent 方法,就可以完成这些定制化的需求

由于篇幅有限,有关 ApplicationContextInitializer 更详细的内容,笔者后续专门讲解

2.5 加载 ApplicationListener

setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

上述代码用于加载并初始化 ApplicationListener 及其相关的类。

ApplicationListenerSpring 框架提供的一个事件监听机制,它是Spring 应用内部事件驱动机制,通常被用于监控应用内部运行状况。其实现的原理观察者设计模式,该设计模式的初衷是为了实现系统业务逻辑之间的解耦,从而提升系统的可扩展性可维护性

我们可以通过自定义一个类来实现 ApplicationListener 接口然后在这个类中定义需要监听的事件处理方法。当被监听的事件发生时,Spring 会自动调用这个方法来处理事件例如,在一个 Spring Boot 项目中,我们可能想要在容器启动时执行一些特定的操作,如加载配置等,就可以通过实现 ApplicationListener 接口来完成。

由于篇幅有限,有关 ApplicationListener 更详细的内容,笔者后续专门讲解

2.6 推断应用入口类

最后一步,调用 SpringApplicationdeduceMainApplicationClass 方法来进行入口类的推断

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;
}

上述代码的思路就是:

总结

本篇 Huazie大家初步了解了 SpringApplication 的实例化过程,当然由于篇幅受限,还有些内容暂时无法详解,Huazie 将在后续的博文中继续深入分析

只有了解 Spring Boot 在启动时都做了些什么,我们才能在后续的实践的过程中更好理解其运行机制,以便遇到问题能更快地定位排查,使我们应用能够更容易、更方便地接入 Spring Boot

原文地址:https://blog.csdn.net/u012855229/article/details/134266259

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任

如若转载,请注明出处:http://www.7code.cn/show_25502.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注