Spring Boot 属性加载原理解析

news2024/11/25 21:18:07

基于Spring Boot 3.1.0 系列文章

  1. Spring Boot 源码阅读初始化环境搭建
  2. Spring Boot 框架整体启动流程详解
  3. Spring Boot 系统初始化器详解
  4. Spring Boot 监听器详解
  5. Spring Boot banner详解
  6. Spring Boot 属性配置解析
  7. Spring Boot 属性加载原理解析

在《Spring Boot 框架整体启动流程详解》中,我们了解到有一步是准备环境prepareEnvironment,属性加载就是在这一步开始的。

private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
		DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
	//创建并配置环境
	ConfigurableEnvironment environment = getOrCreateEnvironment();
	//配置环境,如果需要转换服务,添加ApplicationConversionService,另外委托给了configurePropertySources(属性源)和configureProfiles(配置文件),子类可以覆盖该方法或分别覆盖两者进行细粒度控制
	configureEnvironment(environment, applicationArguments.getSourceArgs());
	//将ConfigurationPropertySource支持附加到指定的环境
	ConfigurationPropertySources.attach(environment);
	//调用environmentPrepared方法
	listeners.environmentPrepared(bootstrapContext, environment);
	//将defaultProperties属性源移动到指定配置环境的最后
	DefaultPropertiesPropertySource.moveToEnd(environment);
	Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
			"Environment prefix cannot be set via properties.");
			//绑定环境到SpringApplication
	bindToSpringApplication(environment);
	//非自定义环境配置,就将其转换为标准类型
	if (!this.isCustomEnvironment) {
		EnvironmentConverter environmentConverter = new EnvironmentConverter(getClassLoader());
		environment = environmentConverter.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
	}
	//重新将ConfigurationPropertySource支持附加到指定的环境
	ConfigurationPropertySources.attach(environment);
	return environment;
}

进入getOrCreateEnvironment()

private ConfigurableEnvironment getOrCreateEnvironment() {
//判断environment 是否为null,不为null使用environment
	if (this.environment != null) {
		return this.environment;
	}
	//根据web应用程序类型,通过applicationContextFactory创建environment
	ConfigurableEnvironment environment = this.applicationContextFactory.createEnvironment(this.webApplicationType);
	//如果environment为null,并且applicationContextFactory不是用的默认ApplicationContextFactory
	if (environment == null && this.applicationContextFactory != ApplicationContextFactory.DEFAULT) {
	//使用默认的ApplicationContextFactory创建environment
		environment = ApplicationContextFactory.DEFAULT.createEnvironment(this.webApplicationType);
	}
	//如果不为null返回environment,否则只显示创建一个ApplicationEnvironment
	return (environment != null) ? environment : new ApplicationEnvironment();
}

this.applicationContextFactory 由于没有显示设置,使用的是默认的ApplicationContextFactory
private ApplicationContextFactory applicationContextFactory = ApplicationContextFactory.DEFAULT;
ApplicationContextFactory DEFAULT = new DefaultApplicationContextFactory();

进入createEnvironment(this.webApplicationType)中:

public ConfigurableEnvironment createEnvironment(WebApplicationType webApplicationType) {
	return getFromSpringFactories(webApplicationType, ApplicationContextFactory::createEnvironment, null);
}

进入getFromSpringFactories中:

private <T> T getFromSpringFactories(WebApplicationType webApplicationType,
		BiFunction<ApplicationContextFactory, WebApplicationType, T> action, Supplier<T> defaultResult) {
		//循环获取ApplicationContextFactory类型的实例
	for (ApplicationContextFactory candidate : SpringFactoriesLoader.loadFactories(ApplicationContextFactory.class,
			getClass().getClassLoader())) {
			//调用实例的createEnvironment方法
		T result = action.apply(candidate, webApplicationType);
		if (result != null) {
			return result;
		}
	}
	return (defaultResult != null) ? defaultResult.get() : null;
}

SpringFactoriesLoader.loadFactories(ApplicationContextFactory.class, getClass().getClassLoader())META-INF/spring.factories中获取并实例化ApplicationContextFactory实例,Spring Boot定义了ReactiveWebServerApplicationContextFactoryServletWebServerApplicationContextFactory,所以在这里会分别去调用其中的createEnvironment方法,由于这边是web环境,进入ServletWebServerApplicationContextFactory的createEnvironment中。

public ConfigurableEnvironment createEnvironment(WebApplicationType webApplicationType) {
//不是Web Servlet环境的话返回null,是的话创建一个ApplicationServletEnvironment
	return (webApplicationType != WebApplicationType.SERVLET) ? null : new ApplicationServletEnvironment();
}

进入ApplicationServletEnvironment类中,其继承了StandardServletEnvironment,StandardServletEnvironment类继承了StandardEnvironment并实现了ConfigurableWebEnvironment接口,StandardEnvironment继承了AbstractEnvironment
在这里插入图片描述
在创建ApplicationServletEnvironment的时候,会先创建父类的构造器,所以会先执行AbstractEnvironment的构造器,AbstractEnvironment是Environment的抽象基类

public AbstractEnvironment() {
	this(new MutablePropertySources());
}

MutablePropertySources 是PropertySources接口的默认实现,PropertySources是属性配置源接口,描述了如何获取属性值。

这里再调用了当前类的有参构造器。

protected AbstractEnvironment(MutablePropertySources propertySources) {
	this.propertySources = propertySources;
	//创建配置解析器
	this.propertyResolver = createPropertyResolver(propertySources);
	//调用自定义配置源,具体由子类实现
	customizePropertySources(propertySources);
}

protected void customizePropertySources(MutablePropertySources propertySources) {
}

这里就调用到了StandardServletEnvironmentcustomizePropertySources中:

protected void customizePropertySources(MutablePropertySources propertySources) {
	propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));
	propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));
	if (jndiPresent && JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
		propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));
	}
	super.customizePropertySources(propertySources);
}

在这里添加了关于ServletConfig、ServletContext、JNDI的配置源
在这里插入图片描述
在该方法的最后,又调用到了父类StandardEnvironmentcustomizePropertySources中:

protected void customizePropertySources(MutablePropertySources propertySources) {
	propertySources.addLast(
			new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
	propertySources.addLast(
			new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
}

在这里添加了Java System属性、操作系统环境变量两个配置源
在这里插入图片描述
到此为止已经添加了4个配置源,由于这里不是JNDI环境,没有添加JNDI的配置源,这里执行结束后返回到SpringApplication的getOrCreateEnvironment()处
在这里插入图片描述
接着进入configureEnvironment(environment, applicationArguments.getSourceArgs())

protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
	//这里用于添加转换服务
	if (this.addConversionService) {
		environment.setConversionService(new ApplicationConversionService());
	}
	//这里也是设置配置源,后面详解
	configurePropertySources(environment, args);
	//设置激活的配置文件
	configureProfiles(environment, args);
}

进入configurePropertySources(environment, args)

protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
//获取环境中已有的配置源
	MutablePropertySources sources = environment.getPropertySources();
	//默认配置不为空,则添加到配置源中,defaultProperties通过springApplication.setDefaultProperties(properties) 配置
	if (!CollectionUtils.isEmpty(this.defaultProperties)) {
	//addOrMerge会判断已有的配置源中是否已经存在了defaultProperties,来判断是合并还是直接添加
		DefaultPropertiesPropertySource.addOrMerge(this.defaultProperties, sources);
	}
	//判断是否有命令行参数,addCommandLineProperties表示是否允许添加命令行配置,默认为true,可通过setAddCommandLineProperties配置
	if (this.addCommandLineProperties && args.length > 0) {
	//命令行配置源名称
		String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
		//已有配置源中是否包含命令行配置源名称
		if (sources.contains(name)) {
			PropertySource<?> source = sources.get(name);
			CompositePropertySource composite = new CompositePropertySource(name);
			//创建一个具有新名称的组合配置源
			composite
				.addPropertySource(new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args));
			composite.addPropertySource(source);
			//使用新的替换原来的配置源
			sources.replace(name, composite);
		}
		else {
			//不包含就添加到已有源的最前面
			sources.addFirst(new SimpleCommandLinePropertySource(args));
		}
	}
}

SimpleCommandLinePropertySource 用于解析命令行参数并填充到CommandLineArgs中,解析规则为:

–optName[=optValue]
必须以“–”为前缀,并且可以指定值,也可以不指定值。如果指定了值,则名称和值必须用等号(“=”)分隔,不带空格。该值可以是空字符串(可选)。
有效示例有:
–foo
–foo=
–foo=“”
–foo=bar
–foo=“bar then baz”
–foo=bar,baz,biz
无效示例:
-foo
–foo bar
–foo = bar
–foo=bar --foo=baz --foo=biz

添加完命令行配置源有,进入configureProfiles(environment, args)中,开始设置激活的配置文件:

protected void configureProfiles(ConfigurableEnvironment environment, String[] args) {
}

这是一个空的protected方法,可见需要子类去实现,这边没有SpringApplication的子类,也就不会在这里处理。
configureEnvironment处理完后,进入ConfigurationPropertySources.attach(environment)

public static void attach(Environment environment) {
	Assert.isInstanceOf(ConfigurableEnvironment.class, environment);
	MutablePropertySources sources = ((ConfigurableEnvironment) environment).getPropertySources();
	PropertySource<?> attached = getAttached(sources);
	if (attached == null || !isUsingSources(attached, sources)) {
		attached = new ConfigurationPropertySourcesPropertySource(ATTACHED_PROPERTY_SOURCE_NAME,
				new SpringConfigurationPropertySources(sources));
	}
	sources.remove(ATTACHED_PROPERTY_SOURCE_NAME);
	sources.addFirst(attached);
}

该处代码用于将ConfigurationPropertySourcesPropertySource类型的源添加到已有的配置源中,名称为configurationProperties
这里处理完后,会调用listeners.environmentPrepared(bootstrapContext, environment),通过EventPublishingRunListener发送ApplicationEnvironmentPreparedEvent事件,这块前面我们已经多次讲到过,这里不再复述,我们进入EnvironmentPostProcessorApplicationListener,其中的onApplicationEvent在收到ApplicationEnvironmentPreparedEvent事件后,执行onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event)

private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
	ConfigurableEnvironment environment = event.getEnvironment();
	SpringApplication application = event.getSpringApplication();
	for (EnvironmentPostProcessor postProcessor : getEnvironmentPostProcessors(application.getResourceLoader(),
			event.getBootstrapContext())) {
		postProcessor.postProcessEnvironment(environment, application);
	}
}

getEnvironmentPostProcessors(application.getResourceLoader(), event.getBootstrapContext()) 会获取所有的EnvironmentPostProcessor实例,如根据本系列文章的Demo获取到的实例有:

在这里插入图片描述
我们主要关注如下几个,其他的忽略:

  • RandomValuePropertySourceEnvironmentPostProcessor: 添加RandomValuePropertySource 配置源,用来解析RandomValuePropertySource的随机值属性

  • SystemEnvironmentPropertySourceEnvironmentPostProcessor:将原来的SystemEnvironmentPropertySource替换为OriginAwareSystemEnvironmentPropertySource,以便能够跟踪每个属性的SystemEnvironmentOrigin

  • SpringApplicationJsonEnvironmentPostProcessor:添加嵌入在环境变量或系统属性中的SPRING_APPLICATION_JSON 的属性

  • CloudFoundryVcapEnvironmentPostProcessor:如果是Cloud Foundry平台,添加Cloud Foundry相关的配置源

  • ConfigDataEnvironmentPostProcessor:添加application.yml等配置源

  • DevToolsHomePropertiesPostProcessor:添加Devtools 全局配置的配置源

另外@PropertySource注解配置的加载是在刷新上下文中的ConfigurationClassPostProcessor类中处理,具体代码可见ConfigurationClassParser
在这里插入图片描述
17种属性配置的加载基本都在这里了,最后总结一下

总结

在这里插入图片描述

作者其他要推荐的文章,欢迎来学习:
Prometheus 系列文章

  1. Prometheus 的介绍和安装
  2. 直观感受PromQL及其数据类型
  3. PromQL之选择器和运算符
  4. PromQL之函数
  5. Prometheus 告警机制介绍及命令解读
  6. Prometheus 告警模块配置深度解析
  7. Prometheus 配置身份认证
  8. Prometheus 动态拉取监控服务
  9. Prometheus 监控云Mysql和自建Mysql

Grafana 系列文章,版本:OOS v9.3.1

  1. Grafana 的介绍和安装
  2. Grafana监控大屏配置参数介绍(一)
  3. Grafana监控大屏配置参数介绍(二)
  4. Grafana监控大屏可视化图表
  5. Grafana 查询数据和转换数据
  6. Grafana 告警模块介绍
  7. Grafana 告警接入飞书通知

Spring Boot Admin 系列

  1. Spring Boot Admin 参考指南
  2. SpringBoot Admin服务离线、不显示健康信息的问题
  3. Spring Boot Admin2 @EnableAdminServer的加载
  4. Spring Boot Admin2 AdminServerAutoConfiguration详解
  5. Spring Boot Admin2 实例状态监控详解
  6. Spring Boot Admin2 自定义JVM监控通知
  7. Spring Boot Admin2 自定义异常监控
  8. Spring Boot Admin 监控指标接入Grafana可视化

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

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

相关文章

MAYA柔体与弹簧一起使用 6个例子

例子2 Q弹 隐藏物体设置移动动画 例子 3 柔体和粒子 例子4 坑的反弹 例子5 例子6

021+limou+C语言内存管理

0.在Linux下验证C语言地址空间排布 这里是limou3434的博文系列。接下来&#xff0c;我会带您了解在C语言程序视角下的内存分布&#xff0c;会涉及到一点操作系统的知识&#xff0c;但是不多&#xff0c;您无需担忧。 注意&#xff1a;只能在Linux下验证&#xff0c;因为Windo…

如何在客户验收环节搞垮一个项目,大佬是有一套方法的

通过产品、UI、开发、测试撸起袖子加油干&#xff0c;经历需求、设计、研发、测试层层关卡终于进入到了期待已久的客户验收环节。在项目的尾声&#xff0c;连空气里都充满了快活的气氛。 而励志要搞垮项目的大佬心里就不爽了“小样儿&#xff0c;你们认为你们就赢了吗&#xf…

Nginx的安装和配置

下载 访问官网&#xff1a;https://nginx.org/ 点击最新的版本下载&#xff0c; 进入详情页&#xff0c;选择下载任意版本 解压编译安装 tar zxvf nginx-1.22.1.tar.gz解压之后得到文件夹 nginx-1.22 安装之前保证使用的工具和库存在 # 安装gcc yum install -y gcc # 安装…

STM32开发——串口通讯(第2篇)——WIFI(Esp8266)

目录 1.ESP8266 作为设备 2.ESP8266作为服务器 注意&#xff1a;1.在中断中一般不直接在中断服务函数里处理数据&#xff0c;而是在收到数据后直接丢给队列&#xff0c;再处理数据&#xff1b; 2.在中断服务函数里尽量减少使用延时函数及打印函数。 1.ESP8266 作为设备 1.1…

mongo副本集的一些操作

开启副本集 修改配置文件/etc/mongod.conf replication:replSetName: main重启mongod相关服务systemctl restart mongod 注意:每个在副本集中的成员&#xff0c;无论主副replSetName都一样&#xff0c;表示一个副本集的名称 如果添加的节点的replSetName和主节点不一致&…

退出卸载企业奇安信360

一般退出&卸载企业奇安信需要密码&#xff0c;然后我们又都不知道密码是多少的情况下怎么退出奇安信呢 1.打开奇安信的设置 2.找到 "防护中心"--"自我保护" 然后点击确定 3.找到奇安信的安装目录 找到"D:\奇安信\360Safe\EntClient\conf"下面…

python带你获取TripAdvisor旅游景点的真实评价

前言 嗨喽&#xff0c;大家好呀~这里是爱看美女的茜茜呐 猫途鹰&#xff08;TripAdvisor&#xff09;是一个旅游点评网站&#xff0c; 如果您想要爬取该网站的数据&#xff0c;需要了解该网站的访问规则和爬取限制。 所使用软件工具&#xff1a; python 3.8 运行代码 pycha…

【PTA】温故知新模拟题

目录 L1-2 日期格式化 输入格式&#xff1a; 输出格式&#xff1a; 输入样例&#xff1a; 输出样例&#xff1a; 代码&#xff1a; L1-4 心理阴影面积 输入格式&#xff1a; 输出格式&#xff1a; 输入样例&#xff1a; 输出样例&#xff1a; 代码&#xff1a; 7-3…

『论文精读』Vision Transformer(VIT)论文解读

『论文精读』Vision Transformer(VIT)论文解读 文章目录 一. 简介二. 模型架构2.1. 关于image presentation2.2. 关于positional encoding2.3. 关于CNNTransformer2.4. 关于输入图片大小 三. 实验部分3.1. 数据集3.2. 模型及变体3.3. 实验结果3.4. 模型可视化 参考文献 论文下…

CSS3_03:各种卡券优惠券模板制作,开箱即用,学得会,用得着

本文首发于微信公众号&#xff1a;布依前端 微信号&#xff1a;qny-1009 转载请注明出处 原创不易&#xff0c;觉得有用的话&#xff0c;多转发点赞支持 作为前端开发者&#xff0c;经常碰到不规则元素需求&#xff0c;尤其是购物类的优惠券&#xff0c;元素长相怪异&#xff0…

looks调色插件 Red Giant Magic Bullet Looks for Mac

Magic Bullet Looks for Mac版是一款looks调色插件&#xff0c;提供强大的外观和色彩校正功能&#xff0c;无论是对初学者还是影视专业制作人员&#xff0c;从冷酷惊艳的的动作场面到红色&#xff0c;暖色的浪漫色调&#xff0c;都可以帮助快速的完成&#xff0c;满足用户的所有…

LabVIEW开发基于Web数字图像处理

LabVIEW开发基于Web数字图像处理 数字图像处理已在各个领域找到了应用&#xff0c;并已成为一个高度活跃的研究领域。实际实施和实验在教育和研究活动中起着不可或缺的作用。为了方便快捷地实施数字图像处理操作&#xff0c;设计了一个先进的基于Web的数字图像处理虚拟实验室&…

vue3中引入tailwingcss

1、安装依赖 cnpm i -D tailwindcss postcss autoprefixer 2、安装完成后&#xff0c;创建tailwind.config.js 和 postcss.config.js配置文件&#xff0c;继续再控制台输入命令如下&#xff1a; npx tailwindcss init -P 3、修改tailwind.config.js content: ["./ind…

<Linux> 进程

文章目录 进程基本概念描述进程-PCBtask_struct-PCB的一种task_ struct内容分类 组织进程查看进程通过系统调用获取进程标示符fork创建子进程进程状态操作系统原理进程状态linux进程状态 优先级基本概念查看系统进程PRI and NI查看进程优先级的命令其他概念 环境变量基本概念常…

又双叒反转?美国院士复现室温超导!

室温超导又双叒反转&#xff1f; 没错&#xff0c;就是今年3月差点掀翻物理界的“21℃室温超导新材料”成果&#xff0c;来自美国罗彻斯特大学Ranga Dias团队。 尽管存在置疑&#xff0c;目前原论文仍然在《自然》期刊上可以查阅、并没有撤稿。 当时国内外很多团队都立刻尝试复…

程序员常用速查表总览

程序员常用速查表总览 文章目录 程序员常用速查表总览linux命令速查表vim命令速查表git命令速查表c知识速查表matplotlib 速查表数据科学方面的速查表-机器学习、概率论等 在使用linux、vims时命令老是忘记&#xff0c;在网上一番翻找&#xff0c;总结了一下文章&#xff0c;特…

如何使用 Python 自动购买 Interpark 演唱会门票 ?

前言 大家早好、午好、晚好吖 ❤ ~欢迎光临本文章 Interpark是韩国的一家知名网上购物网站&#xff0c;成立于1996年。 它是韩国最早开展网上零售业务的公司之一&#xff0c;提供各种产品&#xff0c;包括各种书籍、电子产品、珠宝、户外用品、食品和服装等等。 Interpark还…

String类(Java)

文章目录 1. 介绍2. 分析3. 方法3.1 String()方法3.2 equal()方法3.3 compareTo()方法3.4 contains()方法3.5 toCharArray()方法3.6 trim()方法3.7 valueOf()方法 1. 介绍 A. 类介绍&#xff1a;   Java将字符串看作对象(不同于c语言, c语言直接使用字符数组来表示字符串)&…

新型的类型转换

C 方式的强制类型转换 (Type)Expression Type(Expression) C 方式强制类型转换存在的问题 过于粗暴 任意类型之间都可以进行转换&#xff0c;编译器很难判断其正确性 难于定位 在源码中无法快速定位所有使用强制类型转换的语句 问题 强制类型转换在实际工程中是很难完全…