当前位置: 首页 > article >正文

Spring是如何实现属性占位符解析

Spring属性占位符解析

      • 核心实现思路
      • 1️⃣ 定义占位符处理器类
      • 2️⃣ 处理 BeanDefinition 中的属性
      • 3️⃣ 替换具体的占位符
      • 4️⃣ 加载配置文件
      • 5️⃣ Getter / Setter 方法

源码见:mini-spring

在这里插入图片描述

在使用 Spring 框架开发过程中,为了实现配置的灵活性,通常会借助 .properties.yml 等文件来支持动态参数注入。属性占位符 ${} 的出现,正是为了完成对这些配置值的动态替换。

在动手编码之前,不妨先思考一个问题:Bean 的创建依赖于 BeanDefinition,那么属性替换的动作,自然应当发生在 BeanDefinition 完成初始化之前。换句话说,我们需要找到一个能在 BeanDefinition 加载完成后、Bean 实例化前介入处理的时机。这时候,BeanFactoryPostProcessor 便是最合适的切入点。


核心实现思路

我们需要定义一个类来实现 BeanFactoryPostProcessor 接口,在 Spring 容器启动时,利用其 postProcessBeanFactory 方法,介入 BeanDefinition 的构建过程,提前解析并替换其中的占位符内容。


1️⃣ 定义占位符处理器类

public class PropertyPlaceholderConfigurer implements BeanFactoryPostProcessor {public static final String PLACEHOLDER_PREFIX = "${";public static final String PLACEHOLDER_SUFFIX = "}";private String location;@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {Properties properties = loadProperties();processProperties(beanFactory, properties);}...
}

这段代码展示了核心类的定义和处理流程的入口。通过实现接口方法 postProcessBeanFactory,在 Bean 初始化前加载配置并处理。


2️⃣ 处理 BeanDefinition 中的属性

private void processProperties(ConfigurableListableBeanFactory beanFactory, Properties properties) {String[] beanNames = beanFactory.getBeanDefinitionNames();for (String name : beanNames) {BeanDefinition definition = beanFactory.getBeanDefinition(name);resolvePropertyValues(definition, properties);}
}

该方法遍历所有 Bean 的定义,并逐一处理其中的属性值,检测是否包含占位符格式。


3️⃣ 替换具体的占位符

private void resolvePropertyValues(BeanDefinition beanDefinition, Properties properties) {PropertyValues values = beanDefinition.getPropertyValues();for (PropertyValue pv : values.getPropertyValueList()) {Object val = pv.getValue();if (val instanceof String) {String strVal = (String) val;int start = strVal.indexOf(PLACEHOLDER_PREFIX);int end = strVal.indexOf(PLACEHOLDER_SUFFIX);if (start != -1 && end != -1 && start < end) {String key = strVal.substring(start + 2, end);String resolved = properties.getProperty(key);StringBuffer buffer = new StringBuffer(strVal);buffer.replace(start, end + 1, resolved);values.addPropertyValue(new PropertyValue(pv.getName(), buffer.toString()));}}}
}

这是占位符替换的具体逻辑,目前仅处理格式为 ${xxx} 的情况。解析出 key 后,从已加载的配置文件中获取对应值进行替换。


4️⃣ 加载配置文件

public Properties loadProperties() {try {DefaultResourceLoader loader = new DefaultResourceLoader();Resource resource = loader.getResource(location);Properties props = new Properties();props.load(resource.getInputStream());return props;} catch (IOException e) {throw new BeansException(e.getMessage(), e);}
}

此方法负责从指定路径读取 .properties 文件并转换为 Properties 对象,为后续替换操作提供数据支撑。


5️⃣ Getter / Setter 方法

public String getLocation() {return location;
}
public void setLocation(String location) {this.location = location;
}

通过这些方法配置属性文件的路径,确保配置器能读取到外部参数。

完整代码

/**  * PropertyPlaceholderConfigurer 类实现 BeanFactoryPostProcessor 接口,  * 用于解析并替换 Bean 定义中的占位符。  *  * 该类主要功能是加载属性文件,并在 BeanFactory 中的所有 Bean 定义属性中替换相应的占位符。  *  * @author jixu * @title PropertyPlaceholderConfigurer * @date 2025/5/31 00:35 */
public class PropertyPlaceholderConfigurer implements BeanFactoryPostProcessor {  // 占位符前缀  public static final String PLACEHOLDER_PREFIX = "${";  // 占位符后缀  public static final String PLACEHOLDER_SUFFIX = "}";  // 属性文件路径  private String location;  /**  * 对 BeanFactory 进行后处理的方法。该方法在 Spring 容器实例化所有 bean 之后,但在 bean 初始化之前被调用。  * 实现类可以通过该方法对 BeanFactory 进行自定义的修改或扩展。  *  * @param beanFactory 可配置的 BeanFactory 实例,允许对 bean 定义进行修改或扩展。  */  @Override  public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {  // 加载属性配置文件  Properties properties = loadProperties();  // 属性值替换占位符  processProperties(beanFactory, properties);  }  /**  * 处理属性,替换 BeanFactory 中所有 Bean 定义中的占位符。  *  * @param beanFactory 包含 Bean 定义的 BeanFactory 实例。  * @param properties  加载的属性配置文件。  */  private void processProperties(ConfigurableListableBeanFactory beanFactory, Properties properties) {  String[] beanDefinitionNames = beanFactory.getBeanDefinitionNames();  for (String beanDefinitionName : beanDefinitionNames) {  BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanDefinitionName);  // 判断属性当中是否有占位符存在,如果有则进行替换  resolvePropertyValues(beanDefinition, properties);  }  }  /**  * 解析并替换 Bean 定义属性中的占位符。  *  * @param beanDefinition Bean 定义。  * @param properties     加载的属性配置文件。  */  private void resolvePropertyValues(BeanDefinition beanDefinition, Properties properties) {  PropertyValues propertyValues = beanDefinition.getPropertyValues();  for (PropertyValue propertyValue : propertyValues.getPropertyValueList()) {  Object value = propertyValue.getValue();  if (value instanceof String) {  // TODO 仅简单支持一个占位符的格式  String strVal = (String) value;  StringBuffer buf = new StringBuffer(strVal);  int startIndex = strVal.indexOf(PLACEHOLDER_PREFIX);  int endIndex = strVal.indexOf(PLACEHOLDER_SUFFIX);  if (startIndex != -1 && endIndex != -1 && startIndex < endIndex) {  String propKey = strVal.substring(startIndex + 2, endIndex);  String propVal = properties.getProperty(propKey);  buf.replace(startIndex, endIndex + 1, propVal);  propertyValues.addPropertyValue(new PropertyValue(propertyValue.getName(), buf.toString()));  }  }  }  }  /**  * 加载属性配置文件。  *  * @return 加载的属性配置文件。  */  public Properties loadProperties() {  try {  DefaultResourceLoader loader = new DefaultResourceLoader();  Resource resource = loader.getResource(location);  Properties properties = new Properties();  properties.load(resource.getInputStream());  return properties;  } catch (IOException e) {  throw new BeansException(e.getMessage(), e);  }  }  public String getLocation() {  return location;  }  public void setLocation(String location) {  this.location = location;  }  
}

相关文章:

Spring是如何实现属性占位符解析

Spring属性占位符解析 核心实现思路1️⃣ 定义占位符处理器类2️⃣ 处理 BeanDefinition 中的属性3️⃣ 替换具体的占位符4️⃣ 加载配置文件5️⃣ Getter / Setter 方法 源码见&#xff1a;mini-spring 在使用 Spring 框架开发过程中&#xff0c;为了实现配置的灵活性&#xf…...

数据结构之ArrayList

系列文章目录 目录 系列文章目录 前言 一、数据结构的前置语法 1. 时空复杂度 2. 包装类 3. 泛型 二、ArrayList 和顺序表 1. 顺序表的模拟实现 2. 源码 3. ArrayList 的优缺点 前言 本文介绍数据结构的前置算法&#xff0c;以及 ArrayList 的模拟实现&#xff0c;部…...

DDR4读写压力测试

1.1测试环境 1.1.1整体环境介绍 板卡&#xff1a; pcie-403板卡 主控芯片&#xff1a; Xilinx xcvu13p-fhgb2104-2 调试软件&#xff1a; Vivado 2018.3 代码环境&#xff1a; Vscode utf-8 测试工程&#xff1a; pcie403_user_top 1.1.2硬件介绍 UD PCIe-403…...

uniapp 开发企业微信小程序时,如何在当前页面真正销毁前或者关闭小程序前调用一个api接口

在 UniApp 开发企业微信小程序时&#xff0c;若需在页面销毁或小程序关闭前调用 API 接口&#xff0c;需结合页面生命周期和应用生命周期实现。以下是具体实现方案及注意事项&#xff1a; 一、在页面销毁前调用 API&#xff08;页面级&#xff09; 通过页面生命周期钩子 onUnl…...

WPF 按钮点击音效实现

WPF 按钮点击音效实现 下面我将为您提供一个完整的 WPF 按钮点击音效实现方案&#xff0c;包含多种实现方式和高级功能&#xff1a; 完整实现方案 MainWindow.xaml <Window x:Class"ButtonClickSound.MainWindow"xmlns"http://schemas.microsoft.com/win…...

编写测试用例

测试用例&#xff08;Test Case&#xff09;是用于测试系统的要素集合 目录 编写测试用例作用 编写测试用例要包含七大元素 测试用例的设计方法 1、等价类法 2、边界值法 3、正交表法 4、判定表法 5、错误推测法 6、场景法 编写测试用例作用 1、确保功能全面覆盖…...

解释程序(Python)不需要生成机器码 逐行解析 逐行执行

在计算机组成原理中&#xff0c;解释程序&#xff08;Interpreter&#xff09;通常不会生成独立的机器码&#xff0c;但具体情况取决于解释器的实现方式。以下是详细分析&#xff1a; 1. 传统解释程序&#xff1a;不生成机器码 直接逐行执行&#xff1a; 经典的解释器&#xff…...

每日Prompt:隐形人

提示词 黑色棒球帽&#xff0c;白色抹胸、粉色低腰短裙、白色襪子&#xff0c;黑色鞋子&#xff0c;粉紅色背包&#xff0c;衣服悬浮在空中呈现动态姿势&#xff0c;虚幻引擎渲染风格&#xff0c;高清晰游戏CG质感&#xff0c;户外山林背景&#xff0c;画面聚焦在漂浮的衣服上…...

TensorFlow深度学习实战(19)——受限玻尔兹曼机

TensorFlow深度学习实战&#xff08;19&#xff09;——受限玻尔兹曼机 0. 前言1. 受限玻尔兹曼机1.1 受限玻尔兹曼机架构1.2 受限玻尔兹曼机的数学原理 2. 使用受限玻尔兹曼机重建图像3. 深度信念网络小结系列链接 0. 前言 受限玻尔兹曼机 (Restricted Boltzmann Machine, RB…...

告别手动绘图!基于AI的Smart Mermaid自动可视化图表工具搭建与使用指南

以下是对Smart Mermaid的简单介绍&#xff1a; 一款基于 AI 技术的 Web 应用程序&#xff0c;可将文本内容智能转换为 Mermaid 格式的代码&#xff0c;并将其渲染成可视化图表可以智能制作流程图、序列图、甘特图、状态图等等&#xff0c;并且支持在线调整、图片导出可以Docke…...

【Oracle】安装单实例

个人主页&#xff1a;Guiat 归属专栏&#xff1a;Oracle 文章目录 1. 安装前的准备工作1.1 硬件和系统要求1.2 检查系统环境1.3 下载Oracle软件 2. 系统配置2.1 创建Oracle用户和组2.2 配置内核参数2.3 配置用户资源限制2.4 安装必要的软件包 3. 目录结构和环境变量3.1 创建Ora…...

C++测开,自动化测试,业务(第一段实习)

目录 &#x1f33c;前言 一&#xff0c;实习经历怎么写简历 &#x1f339;业务理解 &#x1f382;结构化表达 二&#xff0c;实习 &#x1f982;技术和流程卡点 &#x1f511;实习收获 / 代码风格 三&#xff0c;测试理论&#xff0c;用例设计&#xff0c;工具链 &…...

QT中更新或添加组件时出现“”qt操作至少需要一个处于启用状态的有效资料档案库“解决方法”

在MaintenanceTool.exe中点击下一步 第一个&#xff1a; 第二个&#xff1a; 第三个&#xff1a; 以上任意一个放入资料库中...

论文速读《UAV-Flow Colosseo: 自然语言控制无人机系统》

论文链接&#xff1a;https://arxiv.org/abs/2505.15725项目主页&#xff1a;https://prince687028.github.io/UAV-Flow/ 0. 简介 近年来&#xff0c;无人机技术蓬勃发展&#xff0c;但如何让无人机像智能助手一样理解并执行人类语言指令&#xff0c;仍是一个前沿挑战。现有研…...

ES6+中Promise 中错误捕捉详解——链式调用catch()或者async/await+try/catch

通过 unhandledrejection 捕捉未处理的 Promise 异常&#xff0c;手动将其抛出&#xff0c;最终让 window.onerror 捕捉&#xff0c;从而统一所有异常的处理逻辑 规范代码&#xff1a;catch&#xff08;onRejected&#xff09;、async...awaittry...catch 在 JavaScript 的 Pro…...

CDN安全加速:HTTPS加密最佳配置方案

CDN安全加速的HTTPS加密最佳配置方案需从证书管理、协议优化、安全策略到性能调优进行全链路设计&#xff0c;以下是核心实施步骤与注意事项&#xff1a; ​​一、证书配置与管理​​ ​​证书选择与格式​​ ​​证书类型​​&#xff1a;优先使用受信任CA机构颁发的DV/OV/EV证…...

解常微分方程组

Euler法 function euler_method % 参数设置 v_missile 450; % 导弹速度 km/h v_enemy 90; % 敌艇速度 km/h % 初始条件 x0 0; % 导弹初始位置 x y0 0; % 导弹初始位置 y xe0 120; % 敌艇初始位置 y t0 0; % 初始时间 % 时间步长和总时间 dt 0.01; % 时间步长 t_final …...

C++实现汉诺塔游戏自动完成

目录 一、汉诺塔的规则二、数学递归推导式三、步骤实现(一)汉诺塔模型(二)递归实现(三)显示1.命令行显示2.SDL图形显示 四、处理用户输入及SDL环境配置五、总结六、源码下载 一、汉诺塔的规则 游戏由3根柱子和若干大小不一的圆盘组成&#xff0c;初始状态下&#xff0c;所有的…...

在 ABP VNext 中集成 Serilog:打造可观测、结构化日志系统

&#x1f680; 在 ABP VNext 中集成 Serilog&#xff1a;打造可观测、结构化日志系统 &#x1f4da; 目录 &#x1f680; 在 ABP VNext 中集成 Serilog&#xff1a;打造可观测、结构化日志系统1. 为什么要使用结构化日志&#xff1f; &#x1f914;2. 核心集成步骤 &#x1f6e…...

pikachu靶场通关笔记07 XSS关卡03-存储型XSS

目录 一、XSS 二、存储型XSS 三、源码分析 四、渗透实战 1、输入mooyuan试一试 2、注入Payload 3、查看数据库 4、再次进入留言板页面 本系列为通过《pikachu靶场通关笔记》的XSS关卡(共10关&#xff09;渗透集合&#xff0c;通过对XSS关卡源码的代码审计找到XSS风险的…...

GitLab CI、GitHub Actions和Jenkins进行比较

特性/工具JenkinsGitLab CIGitHub Actions架构设计哲学Master/Agent分布式架构&#xff0c;通过插件扩展功能代码与CI/CD强耦合&#xff0c;内置Git仓库&#xff0c;基于Runner注册机制事件驱动&#xff0c;与GitHub深度集成&#xff0c;基于虚拟机的Job执行单元核心运行机制支…...

strcat及其模拟实现

#define _CRT_SECURE_NO_WARNINGS strcat 追加字符串 str "string"&#xff08;字符串&#xff09; cat "concatenate"&#xff08;连接 / 追加&#xff09; char* strcat(char* destination, const char* source); strcat的应用 方法一&#xff…...

OpenCV CUDA模块直方图计算------用于在 GPU 上执行对比度受限的自适应直方图均衡类cv::cuda::CLAHE

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 cv::cuda::CLAHE 是 OpenCV 的 CUDA 模块中提供的一个类&#xff0c;用于在 GPU 上执行对比度受限的自适应直方图均衡&#xff08;Contrast Limi…...

华为OD机试真题——矩形绘制(2025A卷:200分)Java/python/JavaScript/C/C++/GO最佳实现

2025 A卷 200分 题型 本专栏内全部题目均提供Java、python、JavaScript、C、C++、GO六种语言的最佳实现方式; 并且每种语言均涵盖详细的问题分析、解题思路、代码实现、代码详解、3个测试用例以及综合分析; 本文收录于专栏:《2025华为OD真题目录+全流程解析+备考攻略+经验分…...

通义开源视觉感知多模态 RAG 推理框架 VRAG-RL:开启多模态推理新时代

通义实验室的自然语言智能团队&#xff0c;凭借深厚的技术积累与创新精神&#xff0c;成功研发并开源了视觉感知多模态 RAG 推理框架 VRAG-RL&#xff0c;为 AI 在复杂视觉信息处理领域带来了重大突破。 传统 RAG 方法的局限 传统的检索增强型生成&#xff08;RAG&#xff0…...

爬虫入门:从基础到实战全攻略

&#x1f9e0; 一、爬虫基础概念 1.1 爬虫定义 爬虫&#xff08;Web Crawler&#xff09;是模拟浏览器行为&#xff0c;自动向服务器发送请求并获取响应数据的一种程序。主要用于从网页中提取结构化数据&#xff0c;供后续分析、展示或存储使用。 1.2 爬虫特点 数据碎片化&…...

qemu安装risc-V 64

参考这篇文章https://developer.aliyun.com/article/1323996&#xff0c;其中在wsl下面安装可能会报错环境变量中有空格。 # clean_path.sh#!/bin/bash# 备份旧 PATH OLD_PATH"$PATH"# 过滤掉包含空格、制表符、换行的路径 CLEAN_PATH"" IFS: read -ra PA…...

JDBC连不上mysql:Unable to load authentication plugin ‘caching_sha2_password‘.

最近为一个spring-boot项目下了mysql-9.3.0&#xff0c;结果因为mysql版本太新一直报错连不上。 错误如下&#xff1a; 2025-06-01 16:19:43.516 ERROR 22088 --- [http-nio-8080-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispat…...

AsyncIOScheduler与BackgroundScheduler的线程模型对比

1. BackgroundScheduler的线程机制‌ ‌多线程模型‌&#xff1a;BackgroundScheduler基于线程池执行任务&#xff0c;默认通过ThreadPoolExecutor创建独立线程处理任务&#xff0c;每个任务运行在单独的线程中&#xff0c;主线程不会被阻塞。‌适用场景‌&#xff1a;适合同步…...

Python+MongoDb使用手册(精简)

这里是学了下面链接的内容&#xff0c;加上一些自己学习的内容综合的&#xff0c;大家也可以去看看这篇文章&#xff0c;写的特别好 【python】在Python中操作MongoDB的详细用法教程与实战案例分享_python轻松入门&#xff0c;基础语法到高阶实战教学-CSDN专栏 1 库&#xff1…...