springboot配置注入增强(二)属性注入的原理
一 原理
1 配置的存储
springboot在启动的时候会后构建一个org.springframework.core.env.Environment类型的对象,这个对象就是用于存储配置,如图springboot会在启动的最开始创建一个Environment对象



这个webApplicationType的枚举是在new SpringApplication()时候指定的


- 如果org.springframework.web.reactive.DispatcherHandler存在并且可加载(他本身或其依赖项之一不存在或无法加载),并且org.springframework.web.servlet.DispatcherServlet不存在或不可加载,并且org.glassfish.jersey.servlet.ServletContainer不存在或不可加载,那么就会使用WebApplicationType.REACTIVE,构建ApplicationReactiveWebEnvironment类型的Environment对象,即Spring WebFlux框架
- 如果javax.servlet.Servlet,org.springframework.web.context.ConfigurableWebApplicationContext中任何一个不存在或不可加载,那么就会使用WebApplicationType.NONE,构建ApplicationEnvironment类型的Environment对象,即普通spring非web框架
- 否则就会使用WebApplicationType.SERVLET,构建ApplicationServletEnvironment类型的Environment对象,即Servlet也就是spring mvc框架
我们以常用的spring mvc为例,先看下ApplicationServletEnvironment类的数据结构(都大同小异)

本质是一个PropertyResolver接口,核心是能提供一个根据配置中某个属性的key获取对应属性值的方法和跟据某个规则解析属性值的方法,他所有的子类都是在对其做一些扩张,让其使用更方便,比如ConfigurableEnvironment增加的MutablePropertySources getPropertySources()方法,就是为了能获取到全部的配置内容

其实springboot的这三种Environment都是StandardEnvironment的子类,而StandardEnvironment的父类AbstractEnvironment使用了MutablePropertySources作为数据源集合的类型,当然它也是根据其实现接口ConfigurableEnvironment中的MutablePropertySources getPropertySources()方法来确定数据源集合的类型。

可以看出来这个类对比原始的PropertyResolver接口多一个数据源集合,springboot的配置原理简单来说就是将不同来源的配置组装成不同数据源类型的数据源对象,然后放到MutablePropertySources中根据名称和数据源对象进行key-value存储,使用的时候遍历MutablePropertySources中所有的数据源的value,从中找到第一个符合条件的值,找到之后再进行解析,比如${xxxx}这种。这个使用的逻辑即PropertyResolver接口的全部实现是由PropertySourcesPropertyResolver对象代理的(在AbstractEnvironment的构造方法中会new一个PropertySourcesPropertyResolver对象)
MutablePropertySources中有一个List<PropertySource<?>> propertySourceList成员变量,这个就是上面说的数据源集合

PropertySource就是具体的配置了,其实也就两个变量,name:数据源名称,source:具体的数据源。这个实现类有很多,我们也可以自己定义,比如自己创建一个类用做source,然后实现PropertySource那几个根据source查询值的方法


2 配置的来源
其实这个配置的来源可以任何时候添加到Environment对象中,只不过如果想让springboot在启动过程中加载bean时使用到我们的数据源,我们应该在PropertySourcesPlaceholderConfigurer的postProcessBeanFactory()方法执行前加进Environment中,低版本的springboot用的是PropertyPlaceholderConfigurer,不过这个早就已经弃用了所以也就不用管他了,下面我们介绍下PropertySourcesPlaceholderConfigurer

可以看到他是一个BeanFactoryPostProcessor实现类,这个类会在启动的refresh阶段执行postProcessBeanFactory()方法

可以看到,这里会新建一个数据源集合,并且把environment和localProperties加进去,这个localProperties就是我们手动构建PropertySourcesPlaceholderConfigurer时指定的配置文件路径
@Beanpublic static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();configurer.setLocation(new ClassPathResource("config.properties"));return configurer;}
之后会将这个数据源集合封装为一个StringValueResolver用于解析属性


可以看到这个先被用于解析bean的属性如这种的${xx},其实我感觉这个也是为了兼容老代码,毕竟最早的spring也只有在配置文件里注入的这种方式
<bean id="cacheService" class="my.user.UserImpl"><property name="name" value="${user.123.name}"/></bean>
然后可以看到执行了beanFactoryToProcess.addEmbeddedValueResolver(valueResolver)方法,这个方法只是将这个valueResolver解析器存了起来,现在还没用。

会等到解析@value时候在AutowiredAnnotationBeanPostProcessor用到,下面介绍几个系统配置的常用的数据源
2.1 系统的环境变量和java启动时的启动参数
StandardEnvironment在初始化时会由父类执行StandardEnvironment的customizePropertySources方法创建两个数据源放到数据源集合,这两个数据源我们也很熟悉,就是系统的环境变量和java启动时的启动参数即System.getenv()和System.getProperties()

2.2 application.yml
旧版本是由ConfigFileApplicationListener作为启动监听器,在监听到ApplicationEnvironmentPreparedEvent事件也就是创建完Environment后会发的一个事件中加载的

可以看到他也实现了EnvironmentPostProcessor接口,并把自身和其他spring.factories文件中配置的org.springframework.boot.env.EnvironmentPostProcessor=xxxx,合并一起执行对应的postProcessEnvironment()方法,而他本身的postProcessEnvironment方法会加载application.yml文件
新版本是用ConfigDataEnvironmentPostProcessor加载的,同时ConfigFileApplicationListener被废弃了,改为由EnvironmentPostProcessorApplicationListener来执行EnvironmentPostProcessor的方法,职责单一更清晰了些



2.3 PropertySources/PropertySource
PropertySources其实就是PropertySource的集合,是由ConfigurationClassPostProcessor将PropertySource路径的配置文件内容添加到environment中


可以看到这个路径甚至还支持${xxx}这种动态路径(environment.resolveRequiredPlaceholders会将${xxx}从environment对象中获取真正的值),然后将多个location解析后的propertySource组合起来形成一个CompositePropertySource对象

3 配置的使用
3.1 BeanDefinition 的属性注入
上面介绍过,通常就是对BeanDefinition的propertyValues进行解析,在PropertySourcesPlaceholderConfigurer里
<bean id="cacheService" class="my.user.UserImpl"><property name="name" value="${user.123.name}"/></bean>
3.2 @Value
上面也介绍过在AutowiredAnnotationBeanPostProcessor会进行解析
@Value("${user.123.name}")private String user123Name;
3.3 @ConfigurationProperties
@Data
@Component
@ConfigurationProperties(prefix = "user.123")
public class UserConfiguration {/*** 姓名*/private String name;/*** 性别*/private String sex;
}
他是由ConfigurationPropertiesBindingPostProcessor的postProcessBeforeInitialization(Object bean, String beanName)来进行绑定的



可以看到实际上执行的是Binder的bind()方法,这个方法可以将以某个相同前缀的属性绑定到对应对象的属性上,主要关注Binder构造器的前两个参数Iterable<ConfigurationPropertySource> sources, PlaceholdersResolver placeholdersResolver,可以看到都是用propertySources作为数据源来进行查找和解析的,而这个propertySources是ConfigurationPropertiesBinder.register(registry)方法中调用ConfigurationPropertiesBinder.Factory#create()方法中生成的





可以看到如果只有一个PropertySourcesPlaceholderConfigurer类型的bean时,这个数据源就是我们上面说的那个和3.1和3.2所用的一样的数据源PropertySourcesPlaceholderConfigurer的appliedPropertySources。否则就会用Environment作为数据源


3.4 environment
这个就简单了,直接从environment对象取即可
- environment.getProperty("user.123.name")
- environment.resolvePlaceholders("${user.123.name}")
- environment.resolveRequiredPlaceholders("${user.123.name}") 如果解析不到会报错
相关文章:
springboot配置注入增强(二)属性注入的原理
一 原理 1 配置的存储 springboot在启动的时候会后构建一个org.springframework.core.env.Environment类型的对象,这个对象就是用于存储配置,如图springboot会在启动的最开始创建一个Environment对象 这个webApplicationType的枚举是在new SpringAppli…...
Android 使用Camera1实现相机预览、拍照、录像
1. 前言 本文介绍如何从零开始,在Android中实现Camera1的接入,并在文末提供Camera1Manager工具类,可以用于快速接入Camera1。 Android Camera1 API虽然已经被Google废弃,但有些场景下不得不使用。 并且Camera1返回的帧数据是NV21…...
2024字节跳动校招面试真题汇总及其解答(四)
12.Java的类加载机制 Java的类加载机制是指将描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这个过程被称作虚拟机的类加载机制。 类的加载过程分为以下五个阶段: 加载:将Class文件从磁盘读入内存,并…...
网页的快捷方式打开自动全屏--Chrome、Firefox 浏览器相关设置
Firefox 的全屏方式与 Chrome 不同,Chrome 自带全屏模式以及APP模式,通过简单的参数即可设置,而Firefox暂时么有这个功能,Firefox 的全屏功能可以通过全屏插件实现。 全屏模式下,按 F11 不会退出全屏,鼠标…...
LabVIEW使用ModbusTCP协议构建分布式测量系统
LabVIEW使用ModbusTCP协议构建分布式测量系统 分布式测量系统主要用于监控远程物体。这种系统允许对系统用户获得的数据进行全面的数据收集、处理、存储和组织访问。它们可能包括许多不同类型的传感器。 在任何具有互联网接入的个人计算机上运行的软件都会发送来自传感器的测…...
unity学习第1天
本身也具有一些unity知识,包括Eidtor界面使用、Shader效果实现、性能分析,但对C#、游戏逻辑不太清楚,这次想从开发者角度理解游戏,提高C#编程,从简单的unity游戏理解游戏逻辑,更好的为工作服务。 unity201…...
Spring Boot实现对文件进行压缩下载
在Web应用中,文件下载功能是一个常见的需求,特别是当你需要提供用户下载各种类型的文件时。本文将演示如何使用Spring Boot框架来实现一个简单而强大的文件下载功能。我们将创建一个RESTful API,通过该API,用户可以下载问价为ZIP压…...
Mac专用投屏工具AirServer 7 .27 for Mac中文免费激活版
AirServer 7 .27 for Mac中文免费激活版是一款Mac专用投屏工具,能够通过本地网络将音频、照片、视频以及支持AirPlay功能的第三方App,从 iOS 设备无线传送到 Mac 电脑的屏幕上,把Mac变成一个AirPlay终端的实用工具。 目前最新的AirServer 7.2…...
LabVIEW使用巴特沃兹低通滤波器过滤噪声
LabVIEW使用巴特沃兹低通滤波器过滤噪声 设备采集到的数据往往都有噪声,有时候这些数据要做判断使用,如果不处理往往会影响最终的结果。可以使用动态平滑,或者中值滤波等方法。这里介绍使用巴特沃斯低通滤波,也是非常方便的。 下…...
【Realtek sdk-3.4.14b】RTL8197FH-VG和RTL8812F自适应认证失败问题分析及修改
WiFi自适应认证介绍 WiFi 自适应可以理解为针对WiFi的产品,当有外部干扰信号通过,WiFi产品自动停止发出信号一段时间,以达到避让的目的。 问题描述 2.4G和5G WiFi自适应认证失败,信道停止发送信号时间过长,没有在规定时间内停止发包 2.4G截图 问题分析 根据实验室描述可以…...
SpringBoot 的版本、打包、Maven
一、SpringBoot 结构、集成 1.1、集成组件 Spring Core:Spring的核心组件,提供IOC、AOP等基础功能,是Spring全家桶的基础。 Spring Boot:一个基于Spring Framework的快速开发框架,可以快速创建独立的、生产级别的…...
不同类型程序的句柄研究
先做一个winform程序;随便放几个控件; 用窗口句柄查看工具看一下;form和上面的每个控件都有一个句柄; 然后看一下记事本;记事本一共包含三个控件,各自有句柄; 这工具的使用是把右下角图标拖到要…...
【Godot】解决游戏中的孤立/孤儿节点及分析器性能问题的分析处理
Godot 4.1 因为我在游戏中发现,越运行游戏变得越来越卡,当你使用 Node 节点中的 print_orphan_nodes() 方法打印信息的时候,会出现如下的孤儿节点信息 孤儿节点信息是以 节点实例ID - Stray Node: 节点名称(Type: 节点类型) 作为格式输出&a…...
国家网络安全宣传周知识竞赛活动小程序界面分享
国家网络安全宣传周知识竞赛活动小程序界面分享...
mysql的判断语句
if if 用于做条件判断,具体的语法结构如下,在 if 条件判断的结构中, ELSE IF 结构可以有多个,也可以没有。 ELSE 结构可以有,也可以没有。 IF 条件1 THEN ..... ELSEIF 条件2 THEN -- 可选 ..... ELSE -- 可选 .....…...
ArcGIS Maps SDK for JavaScript系列之四:添加自定义底图
目录 Basemap类介绍Basemap类的常用属性Basemap类的常用方法 使用Basemap添加自定义底图引用Basemap引用切片图层创建一个新的Basemap对象将自定义图层应用到地图视图中引入并创建Camera对象引入并创建SceneView对象 Basemap类介绍 Basemap类是ArcGIS Maps SDK for JavaScript…...
Learn Prompt-角色扮演
模拟面试 当你在新闻中读到更多关于ChatGPT的内容时,你会听说ChatGPT可以代替医生、面试官、教师、律师等。但如果你想在实践中使用它,除了使用简单的提示或例子,你还可以根据不同的场景为ChatGPT设置不同的角色,这样我们就可以…...
《动手学深度学习 Pytorch版》 6.1 从全连接层到卷积
6.1.1 不变性 平移不变性(translation invariance): 不管检测对象出现在图像中的哪个位置,神经网络的前面几层应该对相同的图像区域具有相似的反应,即为“平移不变性”。 局部性(locality)&…...
六、数学建模之插值与拟合
1.概念 2.例题和matlab代码求解 一、概念 1.插值 (1)定义:插值是数学和统计学中的一种技术,用于估算在已知数据点之间的未知数据点的值。插值的目标是通过已知数据点之间的某种函数或方法来估计中间位置的数值。插值通常用于数…...
【项目经验】:elementui表格中数字汉字排序问题及字符串方法localeCompare()
一.需求 表格中数字汉字排序,数字按大小排列,汉字按拼音首字母(A-Z)排序。 二.用到的方法 第一步:把el-table-column上加上sortable"custom" <el-table-column prop"date" label"序号…...
RocketMQ延迟消息机制
两种延迟消息 RocketMQ中提供了两种延迟消息机制 指定固定的延迟级别 通过在Message中设定一个MessageDelayLevel参数,对应18个预设的延迟级别指定时间点的延迟级别 通过在Message中设定一个DeliverTimeMS指定一个Long类型表示的具体时间点。到了时间点后…...
【Linux】C语言执行shell指令
在C语言中执行Shell指令 在C语言中,有几种方法可以执行Shell指令: 1. 使用system()函数 这是最简单的方法,包含在stdlib.h头文件中: #include <stdlib.h>int main() {system("ls -l"); // 执行ls -l命令retu…...
Debian系统简介
目录 Debian系统介绍 Debian版本介绍 Debian软件源介绍 软件包管理工具dpkg dpkg核心指令详解 安装软件包 卸载软件包 查询软件包状态 验证软件包完整性 手动处理依赖关系 dpkg vs apt Debian系统介绍 Debian 和 Ubuntu 都是基于 Debian内核 的 Linux 发行版ÿ…...
UDP(Echoserver)
网络命令 Ping 命令 检测网络是否连通 使用方法: ping -c 次数 网址ping -c 3 www.baidu.comnetstat 命令 netstat 是一个用来查看网络状态的重要工具. 语法:netstat [选项] 功能:查看网络状态 常用选项: n 拒绝显示别名&#…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院挂号小程序
一、开发准备 环境搭建: 安装DevEco Studio 3.0或更高版本配置HarmonyOS SDK申请开发者账号 项目创建: File > New > Create Project > Application (选择"Empty Ability") 二、核心功能实现 1. 医院科室展示 /…...
屋顶变身“发电站” ,中天合创屋面分布式光伏发电项目顺利并网!
5月28日,中天合创屋面分布式光伏发电项目顺利并网发电,该项目位于内蒙古自治区鄂尔多斯市乌审旗,项目利用中天合创聚乙烯、聚丙烯仓库屋面作为场地建设光伏电站,总装机容量为9.96MWp。 项目投运后,每年可节约标煤3670…...
稳定币的深度剖析与展望
一、引言 在当今数字化浪潮席卷全球的时代,加密货币作为一种新兴的金融现象,正以前所未有的速度改变着我们对传统货币和金融体系的认知。然而,加密货币市场的高度波动性却成为了其广泛应用和普及的一大障碍。在这样的背景下,稳定…...
代理篇12|深入理解 Vite中的Proxy接口代理配置
在前端开发中,常常会遇到 跨域请求接口 的情况。为了解决这个问题,Vite 和 Webpack 都提供了 proxy 代理功能,用于将本地开发请求转发到后端服务器。 什么是代理(proxy)? 代理是在开发过程中,前端项目通过开发服务器,将指定的请求“转发”到真实的后端服务器,从而绕…...
算法笔记2
1.字符串拼接最好用StringBuilder,不用String 2.创建List<>类型的数组并创建内存 List arr[] new ArrayList[26]; Arrays.setAll(arr, i -> new ArrayList<>()); 3.去掉首尾空格...
C#学习第29天:表达式树(Expression Trees)
目录 什么是表达式树? 核心概念 1.表达式树的构建 2. 表达式树与Lambda表达式 3.解析和访问表达式树 4.动态条件查询 表达式树的优势 1.动态构建查询 2.LINQ 提供程序支持: 3.性能优化 4.元数据处理 5.代码转换和重写 适用场景 代码复杂性…...
