Spring Boot 属性加载原理解析

基于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可视化

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

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

相关文章

【计算机视觉 | 图像分类】arxiv 计算机视觉关于图像分类的学术速递(6月 29 日论文合集)

文章目录 一、分类|识别相关(12篇)1.1 Pseudo-Bag Mixup Augmentation for Multiple Instance Learning Based Whole Slide Image Classification1.2 Improving Primate Sounds Classification using Binary Presorting for Deep Learning1.3 Challenges of Zero-Shot Recognit…

阿里云docker启动xxljob,部署自己的定时任务

本次安装版本xxl-job-admin:2.3.0 一&#xff1a;创建xxl-job数据库的各种表 作者官方地址 下载sql执行 二&#xff1a;docker拉取xxl-job镜像 docker pull xuxueli/xxl-job-admin:2.3.0 三&#xff1a;docker启动xxl-job服务 docker run -e PARAMS"--spring.datasour…

Tensorflow神经网络模型-鲜花种类识别

必应壁纸供图 Tensorflow神经网络模型-鲜花种类识别 数据集&#xff1a;https://download.csdn.net/download/weixin_53742691/87982215 导入相关依赖 import warnings import re from IPython.display import clear_output, display from tkinter import Tk, filedialog fro…

wampServer安装Redis 扩展

第一步&#xff1a;查看php版本信息 使用 phpinfo() 函数查看 PHP 的版本信息&#xff08;用于选择扩展包&#xff09; 版本信息&#xff1a;PHP版本为 8.0.26&#xff0c;编译器版本 Visual C 2019&#xff0c;CPU架构 x64 。 第二步&#xff1a;根据第一步信息的版本选择扩…

基于树莓派4B的YOLOv5-Lite目标检测的移植与部署(含训练教程)

前言&#xff1a;本文为手把手教学树莓派4B项目——YOLOv5-Lite目标检测&#xff0c;本次项目采用树莓派4B&#xff08;Cortex-A72&#xff09;作为核心 CPU 进行部署。该篇博客算是深度学习理论的初步实战&#xff0c;选择的网络模型为 YOLOv5 模型的变种 YOLOv5-Lite 模型。Y…

【AI底层逻辑】——篇章3(上):数据、信息与知识香农信息论信息熵

目录 引入 一、数据、信息、知识 二、“用信息丈量世界” 1、香农信息三定律 2、一条信息的价值 3、信息的熵 总结 引入 AI是一种处理信息的模型&#xff0c;我们把信息当作一种内容的载体&#xff0c;计算机发明以前很少有人思考它的本质是什么。随着通信技术的发展&a…

【ISO26262】汽车功能安全第3部分:概念阶段

GB/T34590《道路车辆 功能安全》分为以下部分: 需要文档的朋友,可以和我联系! tommi_wei@163.com GB/T34590的本部分规定了车辆在概念阶段的要求: ———相关项定义; ———安全生命周期启动; ———危害分析和风险评估;及 ———功能安全概念。 危害事件分类 对于每一个…

wsl子系统Ubuntu18.04,cuDNN安装

如果觉得本篇文章对您的学习起到帮助作用&#xff0c;请 点赞 关注 评论 &#xff0c;留下您的足迹&#x1f4aa;&#x1f4aa;&#x1f4aa; 本文主要wls子系统Ubuntu18.04安装cuDNN&#xff0c;安装cudnn坑巨多&#xff0c;因此记录以备日后查看&#xff0c;同时&#xff0…

GaussDB WDR报告分析

标题 问题描述问题现象告警业务影响原因分析处理方法步骤 1步骤 2步骤 3步骤 4步骤 6步骤 7步骤 8步骤9步骤 10步骤 11步骤 12 问题描述 CPU使用率高。 问题现象 出现CPU使用率超过阈值&#xff0c;CPU使用率快速上涨或短时间持续较高水平等现象。 告警 CPU使用率告警。 …

uniapp的表单校验方式整理

uniapp的表单校验方式整理 这里我使用的模板为&#xff1a; 第一种&#xff1a; uniapp本身自带表单校验的js文件&#xff0c;代码写的很简洁&#xff0c;也是比较全面的 只要按照规则校验即可&#xff0c;下面是对应的校验代码&#xff1a; /** 数据验证&#xff08;表…

PyQt中数据库的访问(一)

访问数据库的第一步是确保ODBC数据源配置成功&#xff0c;我接下来会写数据源配置的文章&#xff0c;请继续关注本栏&#xff01; &#xff08;一&#xff09;数据库连接 self.DBQSqlDatabase.addDatabase("QODBC") self.DB.setDatabaseName("Driver{sqlServer…

ModaHub AI模型开源社区——向量数据库Milvus存储操作教程

目录 存储操作 数据插入 数据落盘 定时触发 客户端触发 缓冲区达到上限触发 数据合并 建立索引 删除 删除集合 删除分区 删除实体 数据段整理 数据读取 常见问题 存储操作 阅读本文前&#xff0c;请先阅读 存储相关概念。 数据插入 客户端通过调用 insert 接…

【计算机视觉】DINO

paper&#xff1a;Emerging Properties in Self-Supervised Vision Transformers 源码&#xff1a;https://github.com/facebookresearch/dino 20230627周二目前只把第一部分看完了。 论文导读&#xff1a;DINO -自监督视觉Transformers - deephub的文章 - 知乎 综述类型&a…

线程不安全举例

1、举例说明集合类线程不安全 &#xff08;1&#xff09;查看源码可证明 看ArrayList源码 没有sync、lock&#xff0c;线程不安全 &#xff08;2&#xff09;创建多个线程写入读取数据 List<String> list new ArrayList<>(); for (int i 1; i <30 ; i) {n…

【Android】Android虚拟机

虚拟机 Android的虚拟机主要有两种&#xff1a;Dalvik 虚拟机和 ART&#xff08;Android Runtime&#xff09;虚拟机。 Dalvik 虚拟机 Dalvik 虚拟机是 Android 早期使用的虚拟机&#xff0c;它基于寄存器架构。从Android 2.2版本开始&#xff0c;支持JIT即时编译&#xff08…

基于多站点集中汇聚需求的远程调用直播视频汇聚平台解决方案

一、行业背景 随着视频汇聚需求的不断提升&#xff0c;智慧校园、智慧园区等项目中需要将各分支机构的视频统一汇聚到总部&#xff0c;进行统一管控&#xff0c;要满足在监控内部局域网、互联网、VPN网络等TCP/IP环境下&#xff0c;为用户提供低成本、高扩展、强兼容、高性能的…

【SpringBoot】基于SSM框架的题库系统的设计与实现

文章结构 课题&#xff1a;一、项目简介主要功能技术选型 二、 模块介绍学生端教师端(一)考试管理(二)试题管理(三)学生成绩管理 管理员三、 B站项目演示地址 四、本项目其余相关博客 课题&#xff1a; 题库系统的设计与实现一、项目简介 简介&#xff1a;主要分为三个端&…

DAY38——动态规划

步骤&#xff1a; 确定dp数组&#xff08;dp table&#xff09;以及下标的含义确定递推公式dp数组如何初始化确定遍历顺序举例推导dp数组 题目一. 斐波那契数列 1. 确定dp数组以及下标的含义 dp[i]的定义为&#xff1a;第i个数的斐波那契数值是dp[i] 2. 确定递推公式 状态…

【Zookeeper】win安装随笔

目录 下载地址下载目标解压后目录结构配置文件配置文件详情伪分布式安装LinuxZooKeeper audit is disabled启动解决报错&#xff1a;SLF4J: Class path contains multiple SLF4J bindings. _ 下载地址 https://zookeeper.apache.org/releases.html 下载目标 记住选择带bin的…

一步一步学OAK之四:实现如何在低延迟下使用高分辨率视频

目录 Setup 1: 创建文件Setup 2: 安装依赖Setup 3: 导入需要的包Setup 4: 创建pipelineSetup 5: 创建节点Setup 6: 设置节点的属性和参数。Setup 7: 建立链接关系Setup 8: 连接设备并启动管道Setup 9: 创建与DepthAI设备通信的输入队列和输出队列Setup 10: 主循环获取视频帧显示…