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