文章目录
先回顾一下我们在 spring 中是如何使用占位符的
application.xml
<!-- 通过配置PropertyPlaceholderConfigurer的bean来扫描 properties 文件 -->
<bean id="placeHolder" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" >
<value>classpath*:*.properties</value >
</property>
</bean>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<!-- 基本属性 url、username、password -->
<property name="url" value="jdbc:mysql://${mysql.host}:${mysql.port}/${mysql.database}"/>
<property name="username" value="${mysql.username}"/>
<property name="password" value="${mysql.passwd}"/>
</bean>
app.properties
mysql.host=127.0.0.1
mysql.port=3306
mysql.database=db_user
mysql.username=root
mysql.passwd=123456
PropertyPlaceholderConfigurer类解析
根据类图大概可以看出来spring实现占位符替换主要涉及到BeanFactoryPostProcessor
接口和PropertyPlaceholderConfigurer
、PlaceholderConfigurerSupport
、PropertyResourceConfigurer
三个类
先来分析BeanFactoryPostProcessor接口
Spring提供了的一种叫做BeanFactoryPostProcessor
的容器扩展机制。它允许我们在容器实例化对象之前,对容器中的BeanDefinition中的信息做一定的修改(比如对某些字段的值进行修改,这就是占位符替换的根本)。
public interface BeanFactoryPostProcessor {
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
}
什么意思呢?就是只要向 Spring 容器注入了 BeanFactoryPostProcessor
接口的实现类,那么在容器实例化对象之前会先调用这个类的postProcessBeanFactory
方法,而PropertyPlaceholderConfigurer
类正是间接实现了BeanFactoryPostProcessor
接口才完成了占位符的实现(看上面的类图)
我们看到这个接口的参数是ConfigurableListableBeanFactory
类的一个对象,实际开发中,我们常使用它的getBeanDefinition()方法获取某个bean的元数据定义:BeanDefinition。它有这些方法:
真正实现来BeanFactoryPostProcessor接口的是PropertyResourceConfigurer类,来看看它如何实现postProcessBeanFactory方法的
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
try {
// 把加载的所有的 properties 文件中的键值对都取出来
Properties mergedProps = mergeProperties();
// Convert the merged properties, if necessary.
convertProperties(mergedProps);
// Let the subclass process the properties.
processProperties(beanFactory, mergedProps);
}
catch (IOException ex) {
throw new BeanInitializationException("Could not load properties", ex);
}
}
容器会首先走到这里的postProcessBeanFactory方法里面,mergeProperties();
把加载的所有的 properties 文件中的键值对都取出来保存在一起,接下来主要看processProperties
方法,这个方法是抽象的,具体在PropertyPlaceholderConfigurer类中实现,来看代码
@Override
protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, Properties props)
throws BeansException {
StringValueResolver valueResolver = new PlaceholderResolvingStringValueResolver(props);
doProcessProperties(beanFactoryToProcess, valueResolver);
}
我们看到它首先是实例化了一个PlaceholderResolvingStringValueResolver对象,先看看PlaceholderResolvingStringValueResolver这个类,这个是PropertyPlaceholderConfigurer的内部类,代码如下
private class PlaceholderResolvingStringValueResolver implements StringValueResolver {
private final PropertyPlaceholderHelper helper;
private final PlaceholderResolver resolver;
public PlaceholderResolvingStringValueResolver(Properties props) {
this.helper = new PropertyPlaceholderHelper(
placeholderPrefix, placeholderSuffix, valueSeparator, ignoreUnresolvablePlaceholders);
this.resolver = new PropertyPlaceholderConfigurerResolver(props);
}
@Override
public String resolveStringValue(String strVal) throws BeansException {
String resolved = this.helper.replacePlaceholders(strVal, this.resolver);
if (trimValues) {
resolved = resolved.trim();
}
return (resolved.equals(nullValue) ? null : resolved);
}
}
可以看到这个类实现了StringValueResolver这个接口,这个类的作用是我们分析到后面再回头来看~
这个对象作为参数传给了doProcessProperties方法,来看看doProcessProperties
这个方法,它是在父类PlaceholderConfigurerSupport中实现的,代码如下
protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess,
StringValueResolver valueResolver) {
BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver);
String[] beanNames = beanFactoryToProcess.getBeanDefinitionNames();
for (String curName : beanNames) {
// Check that we're not parsing our own bean definition,
// to avoid failing on unresolvable placeholders in properties file locations.
if (!(curName.equals(this.beanName) && beanFactoryToProcess.equals(this.beanFactory))) {
BeanDefinition bd = beanFactoryToProcess.getBeanDefinition(curName);
try {
visitor.visitBeanDefinition(bd);
}
catch (Exception ex) {
throw new BeanDefinitionStoreException(bd.getResourceDescription(), curName, ex.getMessage(), ex);
}
}
}
// New in Spring 2.5: resolve placeholders in alias target names and aliases as well.
beanFactoryToProcess.resolveAliases(valueResolver);
// New in Spring 3.0: resolve placeholders in embedded values such as annotation attributes.
beanFactoryToProcess.addEmbeddedValueResolver(valueResolver);
}
首先根据传进来的valueResolver对象生成了一个BeanDefinitionVisitor对象,然后循环遍历 beanFactory 里面的每一个 bean,获取他们的BeanDefinition,然后调用了visitor.visitBeanDefinition(bd);
,所以重点就是BeanDefinitionVisitor这个对象的visitBeanDefinition方法到底对我们的BeanDefinition做了什么羞羞的事情,继续往下看~
截取了BeanDefinitionVisitor这个类的比较重要的部分代码
public class BeanDefinitionVisitor {
private StringValueResolver valueResolver;
public BeanDefinitionVisitor(StringValueResolver valueResolver) {
Assert.notNull(valueResolver, "StringValueResolver must not be null");
this.valueResolver = valueResolver;
}
public void visitBeanDefinition(BeanDefinition beanDefinition) {
visitParentName(beanDefinition);
visitBeanClassName(beanDefinition);
visitFactoryBeanName(beanDefinition);
visitFactoryMethodName(beanDefinition);
visitScope(beanDefinition);
//重点是这行,调用visitPropertyValues
visitPropertyValues(beanDefinition.getPropertyValues());
ConstructorArgumentValues cas = beanDefinition.getConstructorArgumentValues();
visitIndexedArgumentValues(cas.getIndexedArgumentValues());
visitGenericArgumentValues(cas.getGenericArgumentValues());
}
protected void visitPropertyValues(MutablePropertyValues pvs) {
PropertyValue[] pvArray = pvs.getPropertyValues();
for (PropertyValue pv : pvArray) {
//调用resolveValue
Object newVal = resolveValue(pv.getValue());
if (!ObjectUtils.nullSafeEquals(newVal, pv.getValue())) {
pvs.add(pv.getName(), newVal);
}
}
}
protected Object resolveValue(Object value) {
// 省略部分代码
if (value instanceof String) {
return resolveStringValue((String) value);
}
return value;
}
protected String resolveStringValue(String strVal) {
// 省略部分代码
String resolvedValue = this.valueResolver.resolveStringValue(strVal);
// Return original String if not modified.
return (strVal.equals(resolvedValue) ? strVal : resolvedValue);
}
}
首先看到构造方法,他接收一个StringValueResolver接口的实现类实例作为成员变量,最终在resolveStringValue这个方法中调用this.valueResolver.resolveStringValue(strVal)
发挥作用,这个后面说
来看看这个很关键的visitBeanDefinition方法,visitPropertyValues(beanDefinition.getPropertyValues());
这一行中beanDefinition.getPropertyValues()
取出了这个 bean 的所有属性,在这里我们看到了我们熟悉的占位符,神秘的面纱马上揭开,嘿嘿
在visitPropertyValues调用了resolveValue去取这个属性的值对应的最终的结果,因为占位符是 string 的形式,所以最后会落到resolveStringValue方法中,在这个方法中我们可以看到String resolvedValue = this.valueResolver.resolveStringValue(strVal);
,这里的 strVal 就是图中看到的${mysql.druid.initialSize}
,而this.valueResolver
正是构造函数传进来的参数,也就是PlaceholderConfigurerSupport调用BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver);
作为参数传过来的的valueResolver,这个对象是最开始提到的PlaceholderResolvingStringValueResolver这个类,所以应该就是这个类的resolveStringValue方法把${mysql.druid.initialSize}
替换成 properties 文件中对应的值的
现在重点就是PlaceholderResolvingStringValueResolver这个类
前面说了这个类是PropertyPlaceholderConfigurer的内部类,代码如下
private class PlaceholderResolvingStringValueResolver implements StringValueResolver {
private final PropertyPlaceholderHelper helper;
private final PlaceholderResolver resolver;
public PlaceholderResolvingStringValueResolver(Properties props) {
this.helper = new PropertyPlaceholderHelper(
placeholderPrefix, placeholderSuffix, valueSeparator, ignoreUnresolvablePlaceholders);
this.resolver = new PropertyPlaceholderConfigurerResolver(props);
}
@Override
public String resolveStringValue(String strVal) throws BeansException {
String resolved = this.helper.replacePlaceholders(strVal, this.resolver);
if (trimValues) {
resolved = resolved.trim();
}
return (resolved.equals(nullValue) ? null : resolved);
}
}
通过代码可以看到,占位符的替换主要就是在PropertyPlaceholderHelper类的replacePlaceholders方法中实现的,这个方法代码有点长就不粘贴出来了,感兴趣的可以自己看看PropertyPlaceholderHelper源码,也比较简单,总结起来就是找到参数strVal中被#{
和}
包含的部分,然后调用传入的resolver的resolvePlaceholder方法找到对应的值来进行替换(如果有嵌套的会递归替换),来看看这个PlaceholderResolver接口的实现类PropertyPlaceholderConfigurerResolver。
PlaceholderResolver接口是在PropertyPlaceholderHelper类中定义的一个内部类接口,PropertyPlaceholderConfigurer中定义了内部类PropertyPlaceholderConfigurerResolver实现了这个接口,代码如下
private class PropertyPlaceholderConfigurerResolver implements PlaceholderResolver {
private final Properties props;
private PropertyPlaceholderConfigurerResolver(Properties props) {
this.props = props;
}
@Override
public String resolvePlaceholder(String placeholderName) {
return PropertyPlaceholderConfigurer.this.resolvePlaceholder(placeholderName, props, systemPropertiesMode);
}
}
resolvePlaceholder实现的逻辑如下,说白了就是去 Properties 对象中去对应的值,这里的 props 就是最开始根据 properties 文件生成的,所以到这里应该就真相大白了,其实还是很简单的。
protected String resolvePlaceholder(String placeholder, Properties props, int systemPropertiesMode) {
String propVal = null;
if (systemPropertiesMode == SYSTEM_PROPERTIES_MODE_OVERRIDE) {
propVal = resolveSystemProperty(placeholder);
}
if (propVal == null) {
propVal = resolvePlaceholder(placeholder, props);
}
if (propVal == null && systemPropertiesMode == SYSTEM_PROPERTIES_MODE_FALLBACK) {
propVal = resolveSystemProperty(placeholder);
}
return propVal;
}
protected String resolvePlaceholder(String placeholder, Properties props) {
return props.getProperty(placeholder);
}