从0开始深入理解Spring(1)--SpringApplication构造

从0开始深入理解Spring-启动类构造

引言:
从本篇开始,打算深入理解Spring执行过程及原理,个人理解极有可能有偏差,评论区欢迎指正错误,下面开始进入正片内容。
ps: springboot版本使用2.4.8

Springboot项目启动时,是通过main方法中编写启动类的形式启动的,按照下面格式编写启动类

@SpringBootApplication
public class SpringFrameStudyApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringFrameStudyApplication.class);
    }
}

其核心为SpringApplication.run(SpringFrameStudyApplication.class)方法,下面深入探讨该方法的执行

SpringApplication.java位于org.springframework.boot包下,是Springboot项目启动的核心类。点击run()方法。进入该方法

这里run方法不做过多解析,下篇会详解run方法的具体执行过程

SpringApplication.java

	public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
		return run(new Class<?>[] { primarySource }, args);
	}
	
	public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
		// 在这里创建了SpringApplication对象,并执行run方法。执行完毕后返回配置好的上下文对象ConfigurableApplicationContext
		return new SpringApplication(primarySources).run(args);
	}
	// 构造方法
	public SpringApplication(Class<?>... primarySources) {
		this(null, primarySources);
	}
	
	// SpringApplication核心构造方法
	public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
		// 资源加载器: 如果按照上述方法进行构造时,为null
		this.resourceLoader = resourceLoader;
		Assert.notNull(primarySources, "PrimarySources must not be null");
		this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
		// webApplicationType属性用于判断运行容器: 为Servlet还是Reactive。Reactive为响应式的架构: SpringWebFlex
		this.webApplicationType = WebApplicationType.deduceFromClasspath();
		// 这里重点介绍下。从SpringFactories中获得Boot的注册器及初始化器等相关信息并将springfatory中的信息初始化至缓存中的
		this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories();
		// 设置Application上下文初始化器 相关属性(赋值操作)
		setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
		// 设置应用监听初始化器 相关属性(赋值操作)
		setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
		// 赋值 jvm线程栈中 main方法栈所在类
		this.mainApplicationClass = deduceMainApplicationClass();
	}

这里避免类代码过长过重,拆分两部分进行代码讲解。下面这一部分介绍getBootstrapRegistryInitializersFromSpringFactories()是如何从MATE-INFO/spring.factory中获取相关属性

SpringApplication.java


	private List<BootstrapRegistryInitializer> getBootstrapRegistryInitializersFromSpringFactories() {
		ArrayList<BootstrapRegistryInitializer> initializers = new ArrayList<>();
		// 核心方法,获得Bootstrapper类有关的SpringFactory实例
		getSpringFactoriesInstances(Bootstrapper.class).stream()
				.map((bootstrapper) -> ((BootstrapRegistryInitializer) bootstrapper::initialize))
				.forEach(initializers::add);
		// 获得BootstrapRegistryInitializer类有关的SpringFactory实例
		initializers.addAll(getSpringFactoriesInstances(BootstrapRegistryInitializer.class));
		return initializers;
	}
	
	public void setInitializers(Collection<? extends ApplicationContextInitializer<?>> initializers) {
		this.initializers = new ArrayList<>(initializers);
	}
	
	public void setListeners(Collection<? extends ApplicationListener<?>> listeners) {
		this.listeners = new ArrayList<>(listeners);
	}
	
	/**
     * 根据类型获得SpringFacotries实例
     * @param type
     */
	private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
		return getSpringFactoriesInstances(type, new Class<?>[] {});
	}
	
	/**
     * 根据类型获得SpringFacotries实例
     * @param type 类类型
     * @param parameterTypes 参数类型
     * @param args 构造方法的参数
     */
	private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {
		// 这里getClassLoader()为内部的一个方法
		ClassLoader classLoader = getClassLoader();
		// 根据传入的类获得SpringFactories中的限定名称
		Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
		// 根据名称集合创建实例
		List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
		// 按照order进行排序
		AnnotationAwareOrderComparator.sort(instances);
		return instances;
	}

	public ClassLoader getClassLoader() {
		if (this.resourceLoader != null) {
		    // 正常默认启动时,resourceLoader为空
			return this.resourceLoader.getClassLoader();
		}
		// 默认返回当前主线程
		return ClassUtils.getDefaultClassLoader();
	}

SpringFactoriesLoader.java

	// 获取spring.factories中的所在位置
	public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
	
	public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
		ClassLoader classLoaderToUse = classLoader;
		if (classLoaderToUse == null) {
			classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
		}
		String factoryTypeName = factoryType.getName();
		// 加载所有springfactories的实现类并 根据factoryTypeName获得该实现类的名称
		return loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
	}

	private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
	    // 从缓存中获取已经加载的类 名称
		Map<String, List<String>> result = cache.get(classLoader);
		if (result != null) {
			return result;
		}
		// 如果没有
		result = new HashMap<>();
		try {
		    // 类加载器中获得spring.factories的所在url地址
			Enumeration<URL> urls = classLoader.getResources(FACTORIES_RESOURCE_LOCATION);
			while (urls.hasMoreElements()) {
				URL url = urls.nextElement();
				UrlResource resource = new UrlResource(url);
				// 加载resource资源
				Properties properties = PropertiesLoaderUtils.loadProperties(resource);
				for (Map.Entry<?, ?> entry : properties.entrySet()) {
					String factoryTypeName = ((String) entry.getKey()).trim();
					String[] factoryImplementationNames =
							StringUtils.commaDelimitedListToStringArray((String) entry.getValue());
					for (String factoryImplementationName : factoryImplementationNames) {
						// 添加实现类名称
						result.computeIfAbsent(factoryTypeName, key -> new ArrayList<>())
								.add(factoryImplementationName.trim());
					}
				}
			}

			// 将其列表去重并修改为不可修改的集合
			result.replaceAll((factoryType, implementations) -> implementations.stream().distinct()
					.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList)));
			// 加入缓存中 key为classLoader
			cache.put(classLoader, result);
		}
		catch (IOException ex) {
			throw new IllegalArgumentException("Unable to load factories from location [" +
					FACTORIES_RESOURCE_LOCATION + "]", ex);
		}
		return result;
	}

构造SpringApplication实例的过程实际上是完成了spring.factories文件的扫描,并将扫描好的factories配置放入缓存中(key: className, values: List names)。按照需要去获取设置 上下文、监听等 实现类。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

steam怎么退款?steam退款教程?简单几步即可轻松实现退款

steam怎么退款&#xff1f;steam退款教程&#xff1f;简单几步即可轻松实现退款 说到steam平台大家肯定不会陌生&#xff0c;随着现代的发展&#xff0c;在steam上进行购买游戏已经成了很普遍的东西&#xff0c;但是许多玩家在购买游戏试完之后发现游戏并不符合自己的胃口&…

transformer上手(9)—— 翻译任务

运用 Transformers 库来完成翻译任务。翻译是典型的序列到序列 (sequence-to-sequence, Seq2Seq) 任务&#xff0c;即对于每一个输入序列都会输出一个对应的序列。翻译在任务形式上与许多其他任务很接近&#xff0c;例如&#xff1a; 文本摘要 (Summarization)&#xff1a;将长…

地质灾害监测预警系统:科技守护,构筑智能预警屏障

随着全球气候变化和人为活动的加剧&#xff0c;地质灾害频繁发生&#xff0c;给人们的生命财产安全带来了严重威胁。为了降低地质灾害带来的损失&#xff0c;地质灾害监测预警系统应运而生。本文将为您详细介绍地质灾害监测预警系统的原理、功能以及在实际应用中的效果。 一、地…

【考研数学】全年各阶段用书汇总+资料分享

我一战备考很迷茫&#xff0c;身边室友也都是&#xff0c;和室友一起去买资料&#xff0c;网上推荐的看到了就都买了 大家都不知道怎么样才能选对数学参考书然后快速进入备考状态&#xff0c;最后犹犹豫豫买了一堆资料都没有正式开始备考... 从小都算是身边人口中“偏科&…

L2-3 完全二叉树的层序遍历

完全二叉树的层序遍历 一个二叉树&#xff0c;如果每一个层的结点数都达到最大值&#xff0c;则这个二叉树就是完美二叉树。对于深度为 D 的&#xff0c;有 N 个结点的二叉树&#xff0c;若其结点对应于相同深度完美二叉树的层序遍历的前 N 个结点&#xff0c;这样的树就是完全…

箭头函数有哪些不适用场景

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

预分频器×重装载值)/LSI频率 为什么等于总时间

1. 第一种算法理解&#xff1a;分频系数 64 &#xff0c;外部低速时钟40khz&#xff0c; 则一次计数周期1.6ms &#xff0c;计数625个数&#xff0c;则有625个周期 &#xff0c;1.6ms*625 等于1s 如果分频系数是64&#xff0c;外部低速时钟&#xff08;LSI&#xff09;频率是…

动态规划|416.分割等和子集

力扣题目链接 class Solution { public:bool canPartition(vector<int>& nums) {int sum 0;// dp[i]中的i表示背包内总和// 题目中说&#xff1a;每个数组中的元素不会超过 100&#xff0c;数组的大小不会超过 200// 总和不会大于20000&#xff0c;背包最大只需要其…

STM32标准库+HAL库 | CPU片内FLASH存储器数据掉电读写

一、片内FLASH 在STM32芯片内部有一个FLASH存储器&#xff0c;它主要用于存储代码&#xff0c;我们在电脑上编写好应用程序后&#xff0c;使用下载器把编译后的代码文件烧录到该内部FLASH中&#xff0c; 由于FLASH存储器的内容在掉电后不会丢失&#xff0c;芯片重新上电复位后&…

车载摄像头畸变校正解决方案,打造无畸变高清视界

在车载摄像头日益普及的今天&#xff0c;摄像头图像的畸变问题成为了制约图像质量提升的一大瓶颈。畸变不仅影响画面的美观度&#xff0c;更关键的是它可能导致智能驾驶系统对环境的误判&#xff0c;进而威胁到行车安全。美摄科技凭借其在图像处理领域的深厚实力&#xff0c;推…

双位置继电器RXMD2-1MRK001984 DC220V JOSEF约瑟

系列型号&#xff1a; RXMD2 1MRK 001 984双位置继电器&#xff1b; RXMD2 1MRK 001 985双位置继电器&#xff1b; RXMD2 1MRK 001 986双位置继电器&#xff1b; 用途 该继电器主要用于直流操作的继电保护和自动化回路中作为大容量双稳态元件进行切换和闭锁。 特点 本继电器…

Vue3项目中快速引入ElementUI框架

ElementUI介绍 ElementUI是一个强大的PC端UI组件框架&#xff0c;它不依赖于vue&#xff0c;但是却是当前和vue配合做项目开发的一个比较好的ui框架&#xff0c;其包含了布局&#xff08;layout)&#xff0c;容器&#xff08;container&#xff09;等各类组件&#xff0c;基本上…

Linux-线程

进程 与 线程: 参考自&#xff1a; Linux多线程编程初探 - 峰子_仰望阳光 - 博客园 (cnblogs.com) 进程:   典型的UNIX/Linux进程可以看成只有一个控制线程&#xff1a;一个进程在同一时刻只做一件事情。 有了多个控制线程后&#xff0c;在程序设计时可以把进程设计成在同一时…

代码随想录刷题随记23-回溯3

代码随想录刷题随记23-回溯3 39. 组合总和 leetcode链接 注意同一个 数字可以 无限制重复被选取 怎么体现这个可以重复取的思想很重要 解题代码&#xff1a; class Solution { public:void backtrace( vector<vector<int>>& ret,vector<int> &pat…

Spring Cloud 集成 Redis 发布订阅

目录 前言步骤引入相关maven依赖添加相关配置 使用方法发布订阅发布一个消息 注意总结 前言 在当今的软件开发领域&#xff0c;分布式系统已经成为一种主流的架构模式&#xff0c;尤其是在处理大规模、高并发、高可用的业务场景时。然而&#xff0c;随着系统复杂性的增加&…

Ubuntu20从0开始选择合适版本手动安装cuda,torch-geometric,jax

一个全新的ubuntu20台式机&#xff0c;在Additional Drivers安装nvidia-470-server&#xff08;一开始安装450&#xff0c;cunda版本只能到11.0&#xff0c;torch有些库用不了&#xff0c;可以直接切换点击Apply Changes重启就行&#xff09; nvidia-smi查看CUDA Version可到…

Java基础_22线程死锁,object类下面线程方法,生产者消费者

周二的回顾 1.线程的概念是进程(应用程序软件)最小的基本单位 2.在Java中代码咋写线程1.继承Thread类2.实现Runnable接口3.实现Callable接口 3.Thread相关的方法4.同步锁目的: 当多个线程操作同一个资源的时候&#xff0c;会发生数据不安全性&#xff01;&#xff01;&#x…

Jenkins上面使用pnpm打包

问题 前端也想用Jenkins的CI/CD工作流。 步骤 Jenkins安装NodeJS插件 安装完成&#xff0c;记得重启Jenkins。 全局配置nodejs Jenksinfile pipeline {agent anytools {nodejs "18.15.0"}stages {stage(Check tool version) {steps {sh node -vnpm -vnpm config…

[温故] 红黑树算法

前言 最近在突然想起一些基础的东西, 向着温故知新, 有了些新的感悟和大家分享一下. 排序算法是数据结构的一个重要组成部分, 当时学习的时候没有少折腾, 这里来看看大佬们怎么运用这些数据结构来构建庞大的计算机体系的. 二叉树是排序算法的一个衍生, 基于二叉树的构建不同…

阿里Canal使用

Canal 是阿里巴巴开源的一款基于 MySQL 数据库增量日志解析&#xff0c;提供实时的数据订阅和消费服务的工具。它可以用来读取 MySQL 的 binlog 日志并转换成 JSON 格式的事件消息&#xff0c;然后将这些消息发布到下游的消息中间件&#xff0c;比如 RabbitMQ&#xff0c;以实现…