SpringBoot构造流程源码分析------阶段一

news2024/10/7 20:25:00

SpringApplication的初始化简介

在入口类主要通过SpringApplication的静态方法–run方法进行SpringApplication类的实例化操作,然后再针对实例化对象调用另一个run方法完成整个项目的初始化和启动。本章节重点围绕此过程的前半部分(即SpringApplication类的实例化)来讲解。

public class SpringApplication {
   ......

    public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
        return run(new Class[]{primarySource}, args);
    }

    public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
    	//创建SpringApplication对象并执行某run方法
        return (new SpringApplication(primarySources)).run(args);
    }

  ......

通过入口类的方法进入,可以看到SpringApplication的实例化只是在它提供的静态run方法中创建了一个SpringApplication对象。其中参数primarySources为加载的主要资源类,通常就是SpringBoot的入口类,args为传递给应用程序的参数信息。

SpringApplication实例化流程

上面了解了进行SpringApplication实例化的基本方法,下面我们通过一张简单的流程图来系统地学习在创建SpringApplication对象时都进行了那些核心操作,如下图:
在这里插入图片描述
由上图可以看出,在SpringApplication对象实例化的过程中主要做了三件事:参数赋值给成员变量、应用类型及方法推断和ApplicationContext相关内容加载及实例化。

SpringApplication构造方法参数

下面来看下SpringApplication两个构造方法的核心源代码。

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

    public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
        this.sources = new LinkedHashSet();
        this.bannerMode = Mode.CONSOLE;
        this.logStartupInfo = true;
        this.addCommandLineProperties = true;
        this.addConversionService = true;
        this.headless = true;
        this.registerShutdownHook = true;
        this.additionalProfiles = new HashSet();
        this.isCustomEnvironment = false;
        this.lazyInitialization = false;
        this.resourceLoader = resourceLoader;
        Assert.notNull(primarySources, "PrimarySources must not be null");
        this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
        this.webApplicationType = WebApplicationType.deduceFromClasspath();
        this.setInitializers(this.getSpringFactoriesInstances(ApplicationContextInitializer.class));
        this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
        this.mainApplicationClass = this.deduceMainApplicationClass();
    }

SpringApplication提供了两个构造方法,核心业务逻辑在第二个构造方法中实现。

  • resourceLoader:为资源加载的接口,在Spring Boot启动时打印对应的banner信息,默认采用的就是DefaultResourceLoader。实战过程中,如果程序未按照Spring Boot的“约定”将banner的内容放置于classPath下,或者文件名不是banner.*格式,默认资源加载器是无法加载到对应的banner信息的。此时可通过ResourceLoader来指定需要加载的文件路径。
  • primarySources:为可变参数,默认传入Spring Boot入口类。如果作为项目的引导类,此参数传入的类需要满足一个条件,就是被注解@EnableAutoConfiguration或其组合注解标注。由于@SpringBootApplication注解包含了@EnableAutoConfiguration注解,因此被@SpringBootApplication注解标注的类可作为参数传入。当然,该参数可传入其他普通类。但只有传入被@EnableAutoConfiguration标注的类才能够开启SpringBoot的自动配置。

同时,在SpringApplication类中还提供了追加primarySources的方法,代码如下:

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

回到primarySources参数中,在实例化SpringApplication类过程中并没有对primarySources参数多做处理,只是将其转化为Set集合,并赋值给SpringApplication的私有成员变量Set<Class<?>>primarySources,代码如下:

 public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
       ......
        Assert.notNull(primarySources, "PrimarySources must not be null");
        this.primarySources = new LinkedHashSet(Arrays.asList(primarySources));
       ......
    }

primarySources 私有变量依旧为LinkedHashSet,它具有去重的特性,至此,SpringApplication构造时参数赋值对应变量这一步变完成了。

Web类型推断

完成变量赋值之后,在SpringApplication的构造方法中便调用了WebApplicationType的deduceFromClasspath方法来进行Web应用类型的推断。SpringApplication构造方法相关代码如下:

public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
		......
		this.webApplicationType = WebApplicationType.deduceFromClasspath();
		......
	}

该行代码调用了deduceFromClasspath方法并将获得的web应用类型赋值给私有成员变量webApplicationType。
WebApplicationType为枚举类,它定义了可能的web应用类型,该枚举类提供了一类定义:枚举类型,推断类型的方法和用于推断的常量。枚举类型包括非web应用,基于SERVLET的web应用和基于REACTIVE的web应用。代码如下:

public enum WebApplicationType {
	NONE,
	SERVLET,
	REACTIVE;
	......
}

WebApplicationType内针对web应用类型提供了两个推断方法:deduceFromClasspath和deduceFromApplicationContext方法。再次我们使用了deduceFromClasspath方法,下面重点分析该方法的实现:

public enum WebApplicationType {

	......

	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() {
		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;
	}
	......
}

方法deduceFromClasspath是基于classpath中类是否存在进行类型推断的,就是判断指定的类是否存在于classpath下,并根据判断的结果来进行组合推断该应用属于什么类型。deduceFromPath在判断的过程中用到了ClassUtils的isParent方法。isParent方法的核心机制就是通过反射创建指定的类,根据在创建过程中是否抛出异常来判断该类是否存在。
通过上面的源代码,我们可以看到deduceFromClasspath的推断逻辑如下:

  • 当DispatcherHandler存在,并且DispatcherServlet和ServletContainrer都不存在,则返回类型为WebApplicationType.REACTIVE。
  • 当SERVLET或ConfigurationContext任何一个不存在时,说明当前应用为非Web应用,返回WebApplicationType.NONE。
  • 当应用部位REACTIVE Web应用,并且SERVLET和ConfigurationWebApplcationContext都存在情况下,则为SERVLET的Web应用,返回WebApplicationType.SERVLET。

ApplicationContextInitializer加载

源码分析

applicationContextInitializer是Spring IOC容器提供的一个接口,它是一个回调接口,主要目的是允许用户在ConfigurableApplicationContext类型(或其子类型)的ApplicationContext做refresh方法调用刷新之前,对ConfigurableApplicationContext实例做进一步的社会或处理,通常用于程序上下文进行编程初始化的Web应用程序中。
ApplicationContextInitializer接口只定义了一个initialze方法,代码如下:

public interface ApplicationContextInitializer<C extends ConfigurableApplicationContext> {
    void initialize(C var1);
}

ApplicationContextInitializer接口的initialize方法主要是为了初始化指定的应用上下文。儿对应的上下文有参数传入,参数为ConfigurableApplicationContext的子类。
在完成web应用类型推断,ApplicationContextInitializer便开始进行加载工作,该过程分为两部分:获取相关实例和设置实例。对应的方法为getSpringFactoriesInstances和setInitializers。
SpringApplication中获得实例相关方法源码如下:

private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
		return getSpringFactoriesInstances(type, new Class<?>[] {});
	}

	private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
		ClassLoader classLoader = getClassLoader();
		// Use names and ensure unique to protect against duplicates
		Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
		AnnotationAwareOrderComparator.sort(instances);
		return instances;
	}

getSpringFactoriesInstances方法是通过SpringClassLoader类的loadFactoryName方法来获得META-INF/spring.factories文件中注册的对应配置。在springBoot2.2.1版本找那个,该文件内具体的配置代码如下:

# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer

配置代码中后面的类为接口ApplicationContextInitializer的具体实现类。当获得这些配置的全限定名之后,便可调用createSpringFactoriesInstances方法进行相应的实现操作。

private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
			ClassLoader classLoader, Object[] args, Set<String> names) {
		List<T> instances = new ArrayList<>(names.size());
		//便利加载到的类名(全限定类名)
		for (String name : names) {
			try {
			//获取class
				Class<?> instanceClass = ClassUtils.forName(name, classLoader);
				Assert.isAssignable(type, instanceClass);
				//获取有参构造
				Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
				//创建对象
				T instance = (T) BeanUtils.instantiateClass(constructor, args);
				instances.add(instance);
			}
			catch (Throwable ex) {
				throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
			}
		}
		return instances;
	}

完成获取配置类集合和实例化操作之后,调用setInitializers方法将实例化的集合添加到SpringApplication的成员变量initializers中,类型为List<ApplicationContextInitializer<?>>,代码如下:

public void setInitializers(Collection<? extends ApplicationContextInitializer<?>> initializers) {
		this.initializers = new ArrayList<>(initializers);
	}

setInitializers方法将收到的initializers作为参数创建了一个新的list,并将其赋值给SpringApplication的initializers成员变量。由于是创建了新的List,并且直接赋值,因此该方法一旦被调用,变回导致数据覆盖,使用时需注意。

ApplicationListener加载

完成了ApplicationContextInitializer的加载之后,便会进行ApplicationListener的加载,他的常见应用场景为:当容器初始化完成之后,需要处理一些如数据的加载,初始化缓存、特定任务的注册等。而在此阶段,更多的是用于ApplicationContext管理bean过程的场景。
Spring事件传播机制是基于观察者模式(Observer)实现的,比如,在ApplicationContext管理Bean生命周期的过程中,会将一些改变定义为事件(ApplicationEvent)。ApplicationContext通过ApplicationListener监听ApplicationEvent,当事件被发布之后,ApplicationListener来对事件做出具体的操作。
ApplicationListener的整个配置和加载流程与ApplicationContextInitializer完全一致,也是通过SpringFactoriesLoader的loadFactoryNames方法获得META-INF/spring.factories中对应配置,然后再进行实例化,最后将获得的结果集合添加到SpringApplication的成员变量listeners中,代码如下:

	private List<ApplicationListener<?>> listeners;
	public void setListeners(Collection<? extends ApplicationListener<?>> listeners) {
		this.listeners = new ArrayList<>(listeners);
	}

同样的,在调用setListeners方法时会进行覆盖赋值的操作,之前加载的内容会被清除。
下面我们来看看ApplicationListener这里的基本使用。ApplicationListener接口和ApplicationEvent类配合使用,可实现ApplicationContext的事件处理。如果容器中存在ApplicationListener的Bean,当ApplicationContext调用publishEvent方法时,对应的Bean会被触发。这就是上文提到的观察者模式的实现。
在接口ApplicationListener中只定义了一个onApplicationEvent方法,当监听事件被触发时,onApplicationEvent方法会被执行,接口ApplicationListener的源代码如下:

@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
    void onApplicationEvent(E var1);
}

onApplicationEvent方法一般用于处理应用程序事件,参数event为ApplicationEvent的子类,是具体响应(接收到)的事件。
当ApplicationContext被初始化或刷新时,会触发ContextRefreshedEvent事件。

SpringApplication的定制化配置

基础配置

基础配置与在application.properties文件中的配置一样,用来修改SpringBoot预置参数。比如,我们想在启动程序的时候不打印Banner信息,可以通过在application。properties文件中设置"spring.main.banner-mode=off"来进行关闭。当然,我们也可以通过SpringApplication提供的相关方法来进行同样的操作。例如以下方式:

public static void main(String[] args){
	SpringApplication app = new SpringApplication(MySpringConfiguration.class);
	app.setBannerMode(Banner.Mode.OFF);
	app.run(args);

除了上面讲到的setInitializers个setListeners方法之外,其他的Setter方法都具有类似的功能,比如我们可以通过setWebApplicationType方法来代替SpringBoot默认的自动类型推断。
针对这些settter方法,SpringBoot还专门提供了流式处理类SpringApplicationBuilder,我们将他的功能与SpringApplication逐一对照,可知SpringApplicationBuilder的优点是使代码更简洁,流畅。

配资源配置

除了直接通过Setter方法进行参数的配置,我们还可以通过配置配置源参数对整个配置文件或配置类进行配置。可以通过两个途径进行配置:SpringApplication构造方法参数或SpringApplication提供的setSource方法进行配置。在上面我们已经了解了通过Class<?>…primarySources参数来配置普通类。因此,配置类可通过SpringApplication的构造方法进行指定。但这种方法有一个弊端就是无法指定XML配置和基于package的配置。
另一种配置形式为直接调用setSources方法进行配置,方法源代码如下:

	public void setSources(Set<String> sources) {
		Assert.notNull(sources, "Sources must not be null");
		this.sources = new LinkedHashSet<>(sources);
	}

该方法的参数为String集合,可传递类名,package名称和xml配置资源。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/668110.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

嵌入式系统开发复习指北

【嵌入式系统】20计科3-4班 第1讲 文件IO操作测试 【嵌入式系统】20计科3-4班 第2讲第4讲进程控制与线程测试 【嵌入式系统】20计科3-4班 第3讲进程通信测试 【嵌入式系统】20计科3-4班 第5-6讲内核和BootLoader开发测试 【嵌入式系统】20计科3-4班 第7讲驱动程序开发测试 大题…

PCB设计系列分享-高速ADC布局布线技巧

目录 概要 整体架构流程 技术名词解释 技术细节 1.裸露焊盘 2.最佳连接 3.去耦和层电容 4.PDS的高频层电容 5.分离接地 小结 概要 在当今的工业领域&#xff0c;系统电路板布局已成为设计本身的一个组成部分。因此&#xff0c;设计工程师必须了解影响高速信号链设计性能的机制。…

【操作系统】期末复习汇总最全版本!电子科技大学2023期末考试

操作系统 【考后感悟】本次考试考察了&#xff1a;操作系统的4大特征、线程和进程的区别、页表与页的基本地址变换机构、磁盘调度算法、银行家算法、调度算法&#xff08;短作业优先、时间片轮转&#xff09;、Linux的一些基本知识、shell读程序题以及PV操作编程。知识点基本涵…

目标检测经典工作发展(超详细对比):R-CNN vs SPPNet vs Fast R-CNN vs Faster R-CNN

序 网上关于两阶段目标检测&#xff08;two-stage object detection&#xff09;的几个经典工作R-CNN&#xff0c;SPPNet&#xff0c;Fast R-CNN&#xff0c;Faster R-CNN的发展&#xff0c;各自的优缺点缺乏一个比较清楚的描述&#xff0c;大部分文章讲的比较细节&#xff0c…

代码随想录算法训练营第四十一天| 背包问题

标准背包问题 有n件物品和一个最多能背重量为w 的背包。 第i件物品的重量是weight[i]&#xff0c;得到的价值是value[i] 。每件物品只能用一次&#xff0c;求解将哪些物品装入背包里物品价值总和最大。 举一个例子&#xff1a; 背包最大重量为4。 物品为&#xff1a; 重量价…

c++之qt学习 基本介绍 界面设计 串口

这里写目录标题 qt基类介绍qt不同版本qt下载打开qt creater制作简单qt界面ui界面点击forms&#xff0c;双击ui文件&#xff0c;就可以进入ui编辑器 qt信号和槽给界面增加图片界面布局布局不会影响代码 界面切换更改代码验证账号密码 qt的三驾马车串口助手为下拉框加入属性信息串…

力扣动态规划专题(四)劫舍问题与股票问题 打家劫舍Ⅰ Ⅱ Ⅲ 买卖股票最佳时机Ⅰ Ⅱ Ⅲ IV 步骤及C++实现

文章目录 198. 打家劫舍213. 打家劫舍 II337. 打家劫舍 III121. 买卖股票的最佳时机动态规划贪心算法 122. 买卖股票的最佳时机 II动态规划贪心算法 123.买卖股票的最佳时机III188.买卖股票的最佳时机IV309.最佳买卖股票时机含冷冻期714.买卖股票的最佳时机含手续费 198. 打家劫…

为摸鱼助力:一份Vue3的生成式ElementPlus表单组件

目录 一、实现背景 二、简介 三、组织架构设计 四、实现方式 五、代码示例 六、示例代码效果预览 七、项目预览地址 & 项目源码地址 目前项目还有诸多待完善的地方&#xff0c;大家有好的想法、建议、意见等欢迎再次评论&#xff0c;或于github提交Issues 一、实现…

杭州市等级保护测评机构名录-2023年

等级保护测评机构并不是一成不变的&#xff0c;因为有年审不符合条件被撤销的&#xff0c;也有符合条件新增的&#xff0c;所以需要不定时查看的。这里小编就给大家汇总了2023年杭州市等级保护测评机构名录。 杭州市等级保护测评机构名录-2023年 序号&#xff1a;1 机构名称…

开源SCRM营销平台MarketGo-营销通道

一、概述 互联网逐步由蓝海市场往红海市场走&#xff0c;互联网增量的红利基本到顶了。营销层面过去要获取新用户&#xff0c;现在需要考虑用户的留存、活跃、复购等&#xff0c;重心从拉新向留存用户的精细化运营转移&#xff1b;当人口红利慢慢消失&#xff0c;成本也在逐渐…

零基础学会Python编程——开发环境的搭建

作者简介&#xff1a;一名云计算网络运维人员、每天分享网络与运维的技术与干货。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;网络豆的主页​​​​​​ 目录 前言 学习目标 一.python 介绍 1.Python 的历史 2.Python 的应用领域 二.Pytho…

电源开关这个丨和0哪个在上方?

开关这个丨和0哪个在上方&#xff1f; 开关的I或O的位置&#xff0c;根据安装的方向不同而不同。一般情况下上下方向安装时&#xff0c;都是O在上面。而水平安装时则是左O右I。 开关图片 在这种类型的开关中&#xff0c;是将“|”和“O”作为一个电源开闭循环的标示&#xff0…

PtaPython练习

一、3位水仙花数计算 1、题目 3位水仙花数”是指一个三位整数&#xff0c;其各位数字的3次方和等于该数本身。例如&#xff1a;ABC是一个“3位水仙花数”&#xff0c;则&#xff1a;A的3次方&#xff0b;B的3次方&#xff0b;C的3次方 ABC。‪‬‪‬‪‬‪‬‪‬‮‬‪‬‮‬…

《面试1v1》Spring循环依赖

&#x1f345; 作者简介&#xff1a;王哥&#xff0c;CSDN2022博客总榜Top100&#x1f3c6;、博客专家&#x1f4aa; &#x1f345; 技术交流&#xff1a;定期更新Java硬核干货&#xff0c;不定期送书活动 &#x1f345; 王哥多年工作总结&#xff1a;Java学习路线总结&#xf…

Web前端 3D开发入门规划 3D效果将不再是桌面应用的专利

随着 WEB领域的快速发展 3D技术开始不再是桌面应用的专利 WEB3D技术的应用 实现了启用网址的3维呈现 让界面更直观 立体的展示 他打破了传统平面的展示形式 那么 目前的话 政府也有大量的新基建的项目 如 数字孪生 智慧城市 智慧园区 智慧工厂 智慧消费等等项目都涉及到了 3D…

系统磁盘从MBR格式转换成GPT格式来升级win11

之前的《用移动硬盘当系统盘&#xff0c;即插即用》中说到&#xff0c;需要把磁盘格式转化为MBR格式才能执行下去。问题是&#xff0c;win10升级win11要求启动方式为UEFI的话&#xff0c;磁盘格式不能为MBR。其实不升级也不影响啥&#xff0c;但是就是想好看点。所以花了点时间…

推荐系统学习

推荐系统 系统职能&#xff1a;头条/抖音/快手&#xff0c;都是以推荐系统作为流量的分发的主要手段&#xff1b; 职业发展&#xff1a;大数据处理/流式计算/数据挖掘/机器学习/高并发服务等领域。 更具用户的离十信息和行为&#xff0c;向用户推荐他感兴趣的内容 基于行为的…

Modbus TCP 协议详解及C语言示例

Modbus TCP 是一种应用于以太网的通讯协议&#xff0c;基于Modbus RTU协议。Modbus协议是一种应用于串行数据通信的协议&#xff0c;广泛应用于工业控制系统。Modbus TCP 将传统的 Modbus RTU 消息封装在 TCP/IP 报文中&#xff0c;使其能够在现代的以太网环境中进行通信。本文…

VUE 2X MVVM模型 ③

目录 文章有误请指正&#xff0c;如果觉得对你有用&#xff0c;请点三连一波&#xff0c;蟹蟹支持✨ V u e j s Vuejs Vuejs M V V M MVVM MVVM模型Data与El的2种写法总结 文章有误请指正&#xff0c;如果觉得对你有用&#xff0c;请点三连一波&#xff0c;蟹蟹支持✨ ⡖⠒⠒⠒…

【数据分享】1929-2022年全球站点的逐年平均风速(Shp\Excel\12000个站点)

气象数据是在各项研究中都经常使用的数据&#xff0c;气象指标包括气温、风速、降水、能见度等指标&#xff0c;说到气象数据&#xff0c;最详细的气象数据是具体到气象监测站点的数据&#xff01; 对于具体到监测站点的气象数据&#xff0c;之前我们分享过1929-2022年全球气象…