Spring 源碼解讀 | 佔位符 placeholder 的實現原理

先回顧一下我們在 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接口和PropertyPlaceholderConfigurerPlaceholderConfigurerSupportPropertyResourceConfigurer三個類

先來分析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中被#{}包含的部分,然後調用傳入的resolverresolvePlaceholder方法找到對應的值來進行替換(如果有嵌套的會遞歸替換),來看看這個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);
	}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章