Spring PlaceHolder实现原理解析

      在Spring中,使用properties/yml文件,可以很好的将各种配置项独立在文件中配置。Spring最终从配置文件中读取到的信息替换实际类中的属性值。本文阐述的就是这个过程Spring是如何实现的。这里简单的举个例子。

bean.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans ...>
    <context:property-placeholder location="classpath:/prop.properties" />
    <bean id="user" class="org.zheng.placeholder.User">
        <property name="name" value="zhangsan" />
        <property name="password" value="${password}" />
    </bean>
</beans>

prop.properties

password=999999

User

public class User {
    private String name;
    private String password;
    //getter/setter/tostring
}

UserTest

public class UserTest {
    public static void main(String[] args){
        ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml");
        User user = (User)ac.getBean("user");
        System.out.println(user);
    }
}

输出结果:

User{name='zhangsan', password='999999'}

由此看到password被替换为配置文件中的值。


让我们先来了解下一个接口

      BeanFactoryPostProcessor接口

Spring对这个接口的解释:“Allows for custom modification of an application context's bean definitions,adapting the bean property values of the context's underlying bean factory.....A BeanFactoryPostProcessor may interact with and modify bean definitions, but never bean instances.”。

也就是说允许修改上下文中bean的定义,以及相关bean属性值。并且这个过程是在Bean实例化之前完成的。其实Spring完成占位符替换,最重要的是利用了实现了这个接口的类。在实例化之前,将占位符进行了替换。

再一步步来看:

首先是配置文件信息读取

context:property-placeholder,使用的处理类为:PropertyPlaceholderBeanDefinitionParser,可以从ContextNamespaceHandler中定位到。


PropertyPlaceholderBeanDefinitionParser的getBeanClass方法

@Override
	protected Class<?> getBeanClass(Element element) {
		// As of Spring 3.1, the default value of system-properties-mode has changed from
		// 'FALLBACK' to 'ENVIRONMENT'. This latter value indicates that resolution of
		// placeholders against system properties is a function of the Environment and
		// its current set of PropertySources.
		if (SYSTEM_PROPERTIES_MODE_DEFAULT.equals(element.getAttribute(SYSTEM_PROPERTIES_MODE_ATTRIBUTE))) {
			return PropertySourcesPlaceholderConfigurer.class;
		}

		// The user has explicitly specified a value for system-properties-mode: revert to
		// PropertyPlaceholderConfigurer to ensure backward compatibility with 3.0 and earlier.
		return PropertyPlaceholderConfigurer.class;
	}
,可以看到这个标签其实最终返回的类是PropertyPlaceholderConfigurer,PropertyPlaceholderConfigurer实现了BeanFactoryPostProcessor这个接口。


找到处理方法

/**
     * {@linkplain #mergeProperties Merge}, {@linkplain #convertProperties convert} and
     * {@linkplain #processProperties process} properties against the given bean factory.
     * @throws BeanInitializationException if any properties cannot be loaded
     */
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        try {
            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);
        }
    }
processProperties方法在PropertyPlaceholderConfigurer中实现

	@Override
	protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, Properties props)
			throws BeansException {

		StringValueResolver valueResolver = new PlaceholderResolvingStringValueResolver(props);
		doProcessProperties(beanFactoryToProcess, valueResolver);
	}
doProcessProperties方法
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);
	}
将带有属性值的StringValueResolver对象传递给BeanDefinitionVisitor对象,然后调用visitBeanDefinition来替换占位符。visitBeanDefinition方法的实现如下:
	/**
	 * Traverse the given BeanDefinition object and the MutablePropertyValues
	 * and ConstructorArgumentValues contained in them.
	 * @param beanDefinition the BeanDefinition object to traverse
	 * @see #resolveStringValue(String)
	 */
	public void visitBeanDefinition(BeanDefinition beanDefinition) {
		visitParentName(beanDefinition);
		visitBeanClassName(beanDefinition);
		visitFactoryBeanName(beanDefinition);
		visitFactoryMethodName(beanDefinition);
		visitScope(beanDefinition);
		visitPropertyValues(beanDefinition.getPropertyValues());
		ConstructorArgumentValues cas = beanDefinition.getConstructorArgumentValues();
		visitIndexedArgumentValues(cas.getIndexedArgumentValues());
		visitGenericArgumentValues(cas.getGenericArgumentValues());
	}
针对Bean定义中各种的标签可能出现的情况做枚举,对包含的占位符,做替换。这里看一个方法visitScope,每个的过程大致是类似的。

	protected void visitScope(BeanDefinition beanDefinition) {
		String scope = beanDefinition.getScope();
		if (scope != null) {
			String resolvedScope = resolveStringValue(scope);
			if (!scope.equals(resolvedScope)) {
				beanDefinition.setScope(resolvedScope);
			}
		}
	}

至此,可以了解到Spring是如何实现占位符替换的。

接着做点小扩展,比如说获取到的配置文件中的属性,我们还想进一步处理下再使用,比如得到的密码属性,解密下再赋值,那么怎么做呢?没错,用一个自定义的BeanFactoryPostProcessor实现即可。

public class PasswordPostProcessor implements BeanFactoryPostProcessor {

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        MutablePropertyValues propertyValues = beanFactory.getBeanDefinition("user").getPropertyValues();
        PropertyValue encryPassWordObj = propertyValues.getPropertyValue("password");
        if (encryPassWordObj == null || encryPassWordObj.getValue() == null) {
            return;
        }
        String encryPassword = ((TypedStringValue) encryPassWordObj.getValue()).getValue();
        //模拟解密
        String realPassword = encryPassword + "AAAAA";
        propertyValues.add("password", realPassword);
    }
}

在bean.xml中加句:

<bean id="password_handler" class="org.zheng.placeholder.PasswordPostProcessor" />

运行得到结果:


哈哈,可以看到值变更了。


再多说一句,ClassPathXmlApplicationContext初始化过程,会调用AbstractApplicationContext的invokeBeanFactoryPostProcessors方法,使用PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors来统一处理实现了BeanFacotryPostProcessor接口的类。


项目完整代码:

https://github.com/difffate/SpringProjects

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值