PropertySourcesPlaceholderConfigurer 是 Spring 框架中用于解析属性占位符 (如 ${property.name}) 的类, 它通过读取配置文件 (如 .properties 文件) 并将这些属性值注入到 Spring 的 BeanDefinition 中;
PropertySourcesPlaceholderConfigurer 代替了之前的 PropertyPlaceholderConfigurer,
继承关系
1 | BeanFactoryPostProcessor |
加载时机
springboot 场景
当应用标记了 @EnableAutoConfiguration, springboot 便会激活 AutoConfigurationImportSelector, 通过 SpringFactoriesLoader 扫描到与占位符替换相关的 PropertyPlaceholderAutoConfiguration, 从而自动注入 PropertySourcesPlaceholderConfigurer 类:
1 | false) (proxyBeanMethods = |
PropertySourcesPlaceholderConfigurer 实现了 EnvironmentAware, 其能够感知到 Environment, 并最终获取所有 properties 的真实值: 通过 environment.getPropertySources() 拿到 propertySources:
1 | public class PropertySourcesPlaceholderConfigurer extends PlaceholderConfigurerSupport implements EnvironmentAware { |
而 Environment 是基于 EnvironmentPostProcessor 通过 resourceLoader 读取所有 properties 的:
1 | public class ConfigFileApplicationListener implements EnvironmentPostProcessor, SmartApplicationListener { |
传统 xml 场景
1 | PropertyPlaceholderBeanDefinitionParser -> AbstractPropertyLoadingBeanDefinitionParser -> AbstractSingleBeanDefinitionParser -> AbstractBeanDefinitionParser -> BeanDefinitionParser |
执行过程
PropertySourcesPlaceholderConfigurer 需要在 bean 被实例化之前执行, 也就是说它操纵的不是 spring bean, 而是 BeanDefinition; 如果 PropertySourcesPlaceholderConfigurer 不在 bean 实例化之前处理 BeanDefinition, 那么在 bean 实例化时属性占位符将无法被正确解析, 导致 bean 的属性值不正确;
为了在 Spring 容器实例化 bean 之前完成这些操作,PropertySourcesPlaceholderConfigurer 实现了 BeanFactoryPostProcessor 接口:
- BeanFactoryPostProcessor 允许在 spring 容器实例化 bean 之前对 BeanDefinition 进行修改;
- 在 spring 容器加载 BeanDefinition 之后 & 实例化 bean 之前, BeanFactoryPostProcessor 的 postProcessBeanFactory 方法会被调用;
- PropertyPlaceholderConfigurer 实现了这个方法, 其会遍历所有的 BeanDefinition, 查找其中的属性占位符 (如 ${property.name}), 并从配置文件中读取相应的属性值, 替换这些占位符;
简要过程如下:
1 | // BeanFactoryPostProcessor#postProcessBeanFactory |
在 PlaceholderConfigurerSupport#doProcessProperties 方法中, 会对注册到 beanFactory 中的每一个 beanDefinition 执行扫描:
1 | protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess, |
在 BeanDefinitionVisitor#visitBeanDefinition 方法里, 会对目标 beanDefinition 做一次全身大体检, 所有的 bd 属性均要 resolve 一遍:
1 | public void visitBeanDefinition(BeanDefinition beanDefinition) { |
- 对于 String 类型的属性使用 resolveStringValue 方法解析占位符, 比如: parentClassName、factoryBeanName、scope 等;
- 对于非字符串类型的属性使用 resolveValue 方法解析占位符, 比如: propertyValues、constructorArgumentValues 等;
1
2
3
4
5
6
7
8
9
10
11
12
13
14// visitBeanDefinition 方法片段
if (beanDefinition.hasPropertyValues()) {
visitPropertyValues(beanDefinition.getPropertyValues());
}
protected void visitPropertyValues(MutablePropertyValues pvs) {
PropertyValue[] pvArray = pvs.getPropertyValues();
for (PropertyValue pv : pvArray) {
Object newVal = resolveValue(pv.getValue());
if (!ObjectUtils.nullSafeEquals(newVal, pv.getValue())) {
pvs.add(pv.getName(), newVal);
}
}
}
职责划分
PropertySourcesPlaceholderConfigurer 只负责将占位符解析后填充到 beanDefinition, 而不负责填充到最终 spring bean 的这 “最后一公里”; 将 spring bean 中的占位符最终替换为真实值的逻辑其实另有其人:
- @Value: 通过
AutowiredAnnotationBeanPostProcessor
实现注入:AutowiredAnnotationBeanPostProcessor 类实现了 SmartInstantiationAwareBeanPostProcessor 接口, 在 AbstractAutowireCapableBeanFactory#populateBean 方法中, 会将所有已注册的 InstantiationAwareBeanPostProcessor 执行一遍, 从而完成对占位符的增强替换:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// pvs 是 beanDefinition 的 propertyValues 字段
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
try {
metadata.inject(bean, beanName, pvs);
}
catch (BeanCreationException ex) {
throw ex;
}
catch (Throwable ex) {
throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
}
return pvs;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null);
for (InstantiationAwareBeanPostProcessor bp : getBeanPostProcessorCache().instantiationAware) {
PropertyValues pvsToUse = bp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
if (pvsToUse == null) {
if (filteredPds == null) {
filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
}
pvsToUse = bp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
if (pvsToUse == null) {
return;
}
}
pvs = pvsToUse;
}