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

发布于:2025-06-02 ⋅ 阅读:(23) ⋅ 点赞:(0)

源码见: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;

    @Override
    public 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;  
    }  
}


网站公告

今日签到

点亮在社区的每一天
去签到