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;  }  
}

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

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

相关文章

【大模型面试每日一题】Day 31:LoRA微调方法中低秩矩阵的秩r如何选取?

【大模型面试每日一题】Day 31&#xff1a;LoRA微调方法中低秩矩阵的秩r如何选取&#xff1f; &#x1f4cc; 题目重现 &#x1f31f;&#x1f31f; 面试官:LoRA微调方法中低秩矩阵的秩r如何选取&#xff1f;&#xff1a; #mermaid-svg-g5hxSxV8epzWyP98 {font-family:"…

字节golang后端二面

前端接口使用restful格式&#xff0c;post与get的区别是什么&#xff1f; HTTP网络返回的状态码有哪些&#xff1f; go语言切片与数组的区别是什么&#xff1f; MySQL实现并发安全避免两个事务同时对一个记录写操作的手段有哪些&#xff1f; 如何实现业务的幂等性&#xff08;在…

Spring Security安全实践指南

安全性的核心价值 用户视角的数据敏感性认知 从终端用户角度出发,每个应用程序都涉及不同级别的数据敏感度。以电子邮件服务与网上银行为例:前者内容泄露可能仅造成隐私困扰,而后者账户若被操控将直接导致财产损失。这种差异体现了安全防护需要分级实施的基本原则: // 伪…

Leetcode第451场周赛分析总结

题目链接 竞赛 - 力扣&#xff08;LeetCode&#xff09;全球极客挚爱的技术成长平台 题目解析 A. 3560. 木材运输的最小成本 AC代码 class Solution { public:long long minCuttingCost(int n, int m, int k) {if (n > m) swap(n, m); // n < m;using ll long lon…

Linux中的shell脚本

什么是shell脚本 shell脚本是文本的一种shell脚本是可以运行的文本shell脚本的内容是由逻辑和数据组成shell脚本是解释型语言 用file命令可以查看文件是否是一个脚本文件 file filename 脚本书写规范 注释 单行注释 使用#号来进行单行注释 多行注释 使用 : " 注释内容…

PHP与MYSQL结合中中的一些常用函数,HTTP协议定义,PHP进行文件编程,会话技术

MYSQL&#xff1a; 查询函数: 执行查询语句: 1.mysql_query("SQL语法"); 凡是执行操作希望拿到数据库返回的数据进行展示的(结果返回: 数据结果); 2.执行结果的处理:成功为结果集&#xff0c;失败为false; 成功返回结果:SQL指令没有错误&#xff0c;但是查询结果…

数学分析——一致性(均匀性)和收敛

目录 1. 连续函数 1.1 连续函数的定义 1.2 连续函数的性质 1.2.1 性质一 1.2.2 性质二 1.2.3 性质三 1.2.4 性质四 2. 一致连续函数 2.1 一致连续函数的定义 2.2 一致连续性定理(小间距定理)(一致连续函数的另一种定义) 2.3 一致连续性判定法 2.4 连…

湖北理元理律师事务所:企业债务优化的科学路径与人文关怀

湖北理元理律师事务所&#xff1a;企业债务优化的科学路径与人文关怀 在中小企业经营压力增大的背景下&#xff0c;如何平衡债务清偿与员工生计成为关键课题。湖北理元理律师事务所联合计划集团公司&#xff0c;为服务企业设计了一套兼顾法律合规性与民生保障的债务解决方案&a…

树莓派安装openwrt搭建软路由(ImmortalWrt固件方案)

&#x1f923;&#x1f449;我这里准备了两个版本的openwrt安装方案给大家参考使用&#xff0c;分别是原版的OpenWrt固件以及在原版基础上进行改进的ImmortalWrt固件。推荐使用ImmortalWrt固件&#xff0c;当然如果想直接在原版上进行开发也可以&#xff0c;看个人选择。 &…

一键净化Excel数据:高性能Python脚本实现多核并行清理

摘要 本文分享两个基于Python的Excel数据净化脚本&#xff0c;通过多进程并行技术清除工作表内不可见字符、批注、单元格样式等冗余内容&#xff0c;利用OpenPyXL实现底层操作&#xff0c;结合tqdm进度条和进程级任务分配&#xff0c;可快速处理百万级单元格数据。适用于数据分…

【Netty】EventLoopGroup

在Netty的ServerBootstrap中设置两个EventLoopGroup的作用是将网络操作的两个关键阶段分离到不同的线程组中处理&#xff0c;从而优化性能并简化并发控制。具体来说&#xff1a; 1. 两个EventLoopGroup的角色 第一个EventLoopGroup&#xff08;通常称为bossGroup&#xff09;&…

【前端】Vue中使用CKeditor作为富文本编辑器

官网https://ckeditor.com/ 此处记录一下我在使用的时候具体初始化的代码。 <template><div><textarea :id"id"></textarea></div> </template><script> export default {name: CkEditor,data: function () {return {id:…

前端面经 websocket

应用层协议&#xff0c;实现一个TCP连接上的全双工通信&#xff0c;实时通讯 之前的实时WEB 实现轮询 增加轮询频率 ws wss 明文版本 和 密文版本 特点 # 1 头部小 2 更注重实时性

【笔记】suna部署之获取 Supabase API key 和 project URL

#工作记录 Supabase | The Open Source Firebase Alternative 一、注册与登录 方式一&#xff1a;GitHub 授权登录 在登录页面选择 “继续使用 GitHub” &#xff0c;跳转到 GitHub 授权页面&#xff08;如图 5 所示&#xff09;。确认 “Supabase 的想要访问您的 [账户名] 帐…

爬虫工具链的详细分类解析

以下是针对爬虫工具链的详细分类解析&#xff0c;涵盖静态页面、动态渲染和框架开发三大场景的技术选型与核心特性&#xff1a; &#x1f9e9; 一、静态页面抓取&#xff08;HTML结构固定&#xff09; 工具组合&#xff1a;Requests BeautifulSoup 适用场景&#xff1a;目标数…

STM32F407寄存器操作(ADC非连续扫描模式)

1.前言 书接上回&#xff0c;在看手册的时候我突然发现手册上还描述了另一种ADC扫描模式&#xff0c;即非连续扫描模式&#xff0c;想着连续扫描模式都已经探索过了&#xff0c;那就顺手把非非连续模式研究一下吧。 2.理论 我们先看看手册&#xff0c;这里我就以规则通道举例…

spring切面

概念 两个特点&#xff1a; IOC控制反转AOP主要用来处理公共的代码 例如一个案例就是添加用户&#xff0c;重复的代码包含了记录日志、事务提交和事务回滚等&#xff0c;都是重复的&#xff0c;为了简单&#xff0c;交给AOP来做。 即将复杂的需求分解出不同方面&#xff0c…

[Python] Python中的多重继承

文章目录 Lora中的例子 Lora中的例子 https://github.com/michaelnny/QLoRA-LLM/blob/main/qlora_llm/models/lora.py#L211C1-L243C10如果继承两个父类&#xff0c;并且父类的__init__参数不一样&#xff0c;则可以显式的调用父类init&#xff1b;如果用super().__init__()则需…

rsync服务的搭建

目录 一、rsync介绍 rsync的安装 二、rsync的语法 三、rsync命令使用 1. 本机同步 2. 远程同步 四、rsync作为服务使用 1、尝试启动rsync程序 2、rsync的配置文件介绍 注意事项&#xff1a; 3. rsyncinotify实时同步 3.依赖服务托管xinetd&#xff08;CentOS 6中rs…

【C/C++】面试基础题目收集

C 软件开发面试中常见的刷题题目通常可分为以下几大类&#xff1a;数据结构与算法、系统编程、面向对象设计、C 语言特性、并发编程等。 &#x1f9e0; 一、数据结构与算法&#xff08;力扣/牛客经典题&#xff09; 掌握 STL 和底层结构实现能力&#xff1a; &#x1f4cc; 数…