Spring自定义占位符替换(PropertyPlaceholderConfigurer)

提示:由于作者水平和时间有限,请仅以参考的态度阅读。

引言

在使用SpringMVC做Web开发的时候,为了便于统一管理配置项,常常会看到用占位符的配置方式。这样,可以将分散在spring配置文件中的配置项的值集中到一个(多个)属性文件中,方便管理。

比如定义了一个bean,属性的值使用占位符,如下(applicationContext.xml)

<bean id= "funnelData" class ="com.company.project.web.FunnelData">
       <property name="name" value="${funnel.name}" />
       <property name="value" value="${funnel.value}" />
</bean>

接着在其他properties文件中指定占位符所代表的值,如下(bean.properties)
funnel.name= kiseki
funnel.value=1234

然后告诉spring这个properties文件的位置,这是通过配置PropertyPlaceholderConfigurer的bean来做到的,如下(applicationContext.xml)
<bean id= "placeHolder"
       class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer" >
       <property name="location" >
             <value>classpath:/application.properties</value >
       </property>
</bean>

分析

虽然在3.1开始官方文档推荐优先使用PropertySourcesPlaceholderConfigurer取代PropertyPlaceholderConfigurer,但是研究分析下PropertyPlaceholderConfigurer还是会有不少收获。spring占位符替换主要涉及到BeanFactoryPostProcessor接口和PropertyPlaceholderConfigurer、PlaceholderConfigurerSupport、PropertyResourceConfigurer三个类。



Spring提供了的一种叫做BeanFactoryPostProcessor的容器扩展机制。它允许我们在容器实例化对象之前,对容器中的BeanDefinition中的信息做一定的修改(比如对某些字段的值进行修改,这就是占位符替换的根本)。于是就需要说下BeanFactoryPostProcessor接口了,以下BeanFactoryPostProcessor的定义

public interface BeanFactoryPostProcessor {
     void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
}
在web项目的spring上下文初始化中,spring在实例化bean之前,会先实例化出实现了BeanFactoryPostProcessor接口的bean,并调用postProcessBeanFactory方法,对BeanFactory中的BeanDefinition进行处理。



看看PropertyPlaceholderConfigurer、PlaceholderConfigurerSupport、PropertyResourceConfigurer三个类的定义

public class PropertyPlaceholderConfigurer extends PlaceholderConfigurerSupport
public abstract class PlaceholderConfigurerSupport extends PropertyResourceConfigurer
             implements BeanNameAware, BeanFactoryAware
public abstract class PropertyResourceConfigurer extends PropertiesLoaderSupport
             implements BeanFactoryPostProcessor, PriorityOrdered

可以看到PropertyPlaceholderConfigurer类的祖先类PropertyResourceConfigurer实现了BeanFactoryPostProcessor接口。



先看看PropertyResourceConfigurer的postProcessBeanFactory()方法

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);
   }
}
这个方法整合好Properties,然后以BeanFactory和Properties作为参数调用PropertyPlaceholderConfigurer的processProperties方法。



接着看PropertyPlaceholderConfigurer的processProperties()方法

protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, Properties props)
      throws BeansException {
   StringValueResolver valueResolver = new PlaceholderResolvingStringValueResolver(props);
   this.doProcessProperties(beanFactoryToProcess, valueResolver);
}
这个类就实例化了一个StringValueResolver对象,然后用BeanFactory和StringValueResolver对象调用PlaceholderConfigurerSupport#doProcessProperties()



再看PlaceholderConfigurerSupport的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());
         }
      }
   }

   // 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);
}
这个方法就会取出BeanFactory中的BeanDefinition,然后循环处理除了本身以外的bean的占位符替换

扩展

在了解了PropertyPlaceholderConfigurer做占位符替换的流程之后,我们应该有所启发:

1、要定制自己的占位符替换实现,入口就在BeanFactoryPostProcessor接口。实现BeanFactoryPostProcessor接口,并替换掉PropertyPlaceholderConfigurer即可。

2、占位符替换过程中,最主要的是Properties,整合出自己的Properties后,spring现成的很多代码可以继续使用。


目前我们采用Zookeeper做配置中心,用于管理多个APP实例上的配置,而基本的思路就是实现BeanFactoryPostProcessor接口,从Zookeeper上取相应节点,构造出Properties。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值