Spring 源码解读 | 占位符 placeholder 的实现原理

  • Post author:
  • Post category:其他

先回顾一下我们在 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);
	}

版权声明:本文为ljc1026774829原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。