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