文章目錄
先回顧一下我們在 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);
}