当前位置: 首页 > 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;  }  
}
http://www.lryc.cn/news/2395294.html

相关文章:

  • 数据结构之ArrayList
  • DDR4读写压力测试
  • uniapp 开发企业微信小程序时,如何在当前页面真正销毁前或者关闭小程序前调用一个api接口
  • WPF 按钮点击音效实现
  • 编写测试用例
  • 解释程序(Python)不需要生成机器码 逐行解析 逐行执行
  • 每日Prompt:隐形人
  • TensorFlow深度学习实战(19)——受限玻尔兹曼机
  • 告别手动绘图!基于AI的Smart Mermaid自动可视化图表工具搭建与使用指南
  • 【Oracle】安装单实例
  • C++测开,自动化测试,业务(第一段实习)
  • QT中更新或添加组件时出现“”qt操作至少需要一个处于启用状态的有效资料档案库“解决方法”
  • 论文速读《UAV-Flow Colosseo: 自然语言控制无人机系统》
  • ES6+中Promise 中错误捕捉详解——链式调用catch()或者async/await+try/catch
  • CDN安全加速:HTTPS加密最佳配置方案
  • 解常微分方程组
  • C++实现汉诺塔游戏自动完成
  • 在 ABP VNext 中集成 Serilog:打造可观测、结构化日志系统
  • pikachu靶场通关笔记07 XSS关卡03-存储型XSS
  • GitLab CI、GitHub Actions和Jenkins进行比较
  • strcat及其模拟实现
  • OpenCV CUDA模块直方图计算------用于在 GPU 上执行对比度受限的自适应直方图均衡类cv::cuda::CLAHE
  • 华为OD机试真题——矩形绘制(2025A卷:200分)Java/python/JavaScript/C/C++/GO最佳实现
  • 通义开源视觉感知多模态 RAG 推理框架 VRAG-RL:开启多模态推理新时代
  • 爬虫入门:从基础到实战全攻略
  • qemu安装risc-V 64
  • JDBC连不上mysql:Unable to load authentication plugin ‘caching_sha2_password‘.
  • AsyncIOScheduler与BackgroundScheduler的线程模型对比
  • Python+MongoDb使用手册(精简)
  • 前端面经 协商缓存和强缓存