转自:https://hbiao68.iteye.com/blog/2031006
1.Spring的框架中,org.springframework.beans.factory.config.PropertyPlaceholderConfigurer类可以将.properties(key/value形式)文件中一些动态设定的值(value),在XML中替换为占位该键($key$)的值,.properties文件可以根据客户需求,自定义一些相关的参数,这样的设计可提供程序的灵活性。
2.在Spring中,使用PropertyPlaceholderConfigurer可以在XML配置文件中加入外部属性文件,当然也可以指定外部文件的编码,如:
- <bean id="propertyConfigurerForAnalysis" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
- <property name="location">
- <value>classpath:/spring/include/dbQuery.properties</value>
- </property>
- <property name="fileEncoding">
- <value>UTF-8</value>
- </property>
- </bean>
其中classpath是引用src目录下的文件写法,当存在多个Properties文件时,配置就需使用locations了:
- <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
- <property name="locations">
- <list>
- <value>classpath:/spring/include/jdbc-parms.properties</value>
- <value>classpath:/spring/include/base-config.properties</value>
- <value>classpath*:config/jdbc.properties</value>
- </list>
- </property>
- </bean>
- <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
- <property name="location">
- <value>classpath:hb.properties</value>
- <value>/WEB-INF/config/project/project.properties</value>
- </property>
- </bean>
接下来我们要使用多个PropertyPlaceholderConfigurer来分散配置,达到整合多工程下的多个分散的Properties文件,其配置如下
- <bean id="propertyConfigurerForProject2" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
- <property name="order" value="2" />
- <property name="ignoreUnresolvablePlaceholders" value="true" />
- <property name="locations">
- <list>
- <value>classpath:/spring/include/jdbc-parms.properties</value>
- <value>classpath:/spring/include/base-config.properties</value>
- </list>
- </property>
- </bean>
其中order属性代表其加载顺序,而ignoreUnresolvablePlaceholders为是否忽略不可解析的Placeholder,如配置了多个PropertyPlaceholderConfigurer,则需设置为true
3.譬如,jdbc.properties的内容为:
- jdbc.driverClassName=com.mysql.jdbc.Driver
- jdbc.url=jdbc:mysql://localhost/mysqldb?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=round;
- jdbc.username=root
- jdbc.password=123456
备注:一定要在properties文件中&写为&因为在xml文件中不识别&,必须是&
4.那么在spring配置文件中,我们就可以这样写:
- <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
- <property name="locations">
- <list>
- <value>classpath: conf/sqlmap/jdbc.properties </value>
- </list>
- </property>
- </bean>
- <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
- <property name="driverClassName" value="${jdbc.driverClassName}" />
- <property name="url" value="${jdbc.url}" />
- <property name="username" value="${jdbc.username}" />
- <property name="password" value="${jdbc.password}" />
- </bean>
这样,一个简单的数据源就设置完毕了。可以看出:PropertyPlaceholderConfigurer起的作用就是将占位符指向的数据库配置信息放在bean中定义的工具。
查看源代码,可以发现,locations属性定义在PropertyPlaceholderConfigurer的祖父类PropertiesLoaderSupport中,而location只有setter方法。类似于这样的配置,在spring的源程序中很常见的。
PropertyPlaceholderConfigurer如果在指定的Properties文件中找不到你想使用的属性,它还会在Java的System类属性中查找。
我们可以通过System.setProperty(key, value)或者java中通过-Dnamevalue来给Spring配置文件传递参数。
我们还可以使用注解的方式实现,如:<context:property-placeholder location="classpath:jdbc.properties" />,效果跟PropertyPlaceholderConfigurer是一样的。
原理分析
PropertyPlaceholderConfigurer
类是如何做到xml配置的bean的配置熟悉的运行时替换的呢?这个离不开Spring框架提供的许多扩展点。这个类依赖的扩展点就是BeanFactoryPostProcessors
见下面的类继承图:
BeanFactoryPostProcessors
类有一个方法,提供了在Bean创建前对Bean的Definition进行处理的钩子机制。
1 //在Bean初始化前被调用(例如在InitializingBean的afterPropertiesSet前) 2 void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
从上面的继承图我们知道PropertyPlaceholderConfigurer
继承自类PropertyResourceConfigurer
, 而该类实现了接口BeanFactoryPostProcessors
1 public abstract class PropertyResourceConfigurer extends PropertiesLoaderSupport 2 implements BeanFactoryPostProcessor, PriorityOrdered { 3 @Override 4 public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException { 5 try { 6 Properties mergedProps = mergeProperties(); 7 8 // Convert the merged properties, if necessary. 9 convertProperties(mergedProps); 10 11 // Let the subclass process the properties. 12 processProperties(beanFactory, mergedProps); 13 } 14 catch (IOException ex) { 15 throw new BeanInitializationException("Could not load properties", ex); 16 } 17 } 18 protected abstract void processProperties(ConfigurableListableBeanFactory beanFactory, Properties props) 19 throws BeansException; 20 }
我们的主角PropertyPlaceholderConfigurer
实现了父类中的方法processProperties
, 在该类中对xml配置文件中的展位符进行替换处理
1 /** 2 * Visit each bean definition in the given bean factory and attempt to replace ${...} property 3 * placeholders with values from the given properties. 4 */ 5 @Override 6 protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, Properties props) 7 throws BeansException { 8 9 StringValueResolver valueResolver = new PlaceholderResolvingStringValueResolver(props); 10 doProcessProperties(beanFactoryToProcess, valueResolver); 11 } 12 13 protected void doProcessProperties(ConfigurableListableBeanFactory beanFactoryToProcess, 14 StringValueResolver valueResolver) { 15 16 BeanDefinitionVisitor visitor = new BeanDefinitionVisitor(valueResolver); 17 18 String[] beanNames = beanFactoryToProcess.getBeanDefinitionNames(); 19 for (String curName : beanNames) { 20 // Check that we're not parsing our own bean definition, 21 // to avoid failing on unresolvable placeholders in properties file locations. 22 if (!(curName.equals(this.beanName) && beanFactoryToProcess.equals(this.beanFactory))) { 23 BeanDefinition bd = beanFactoryToProcess.getBeanDefinition(curName); 24 try { 25 visitor.visitBeanDefinition(bd); 26 } 27 catch (Exception ex) { 28 throw new BeanDefinitionStoreException(bd.getResourceDescription(), curName, ex.getMessage(), ex); 29 } 30 } 31 } 32 33 // New in Spring 2.5: resolve placeholders in alias target names and aliases as well. 34 beanFactoryToProcess.resolveAliases(valueResolver); 35 36 // New in Spring 3.0: resolve placeholders in embedded values such as annotation attributes. 37 beanFactoryToProcess.addEmbeddedValueResolver(valueResolver); 38 }
这样就解开了Spring中xml配置文件中占位符的替换魔法功能。
现在想一下,如果我们的配置文件中对某些属性进行了加密,这时再使用 PropertyPlaceholderConfigurer 读取配置文件我们想要加密前的内容该怎么办?
答案就是重写 PropertyPlaceholderConfigurer。除了数据库的配置信息我们放在配置文件,然后可以通过 druid 进行加解密。但是配置的邮箱信息呢?
这时重写它就显得很有必要。PropertyPlaceholderConfigurer起的作用就是将占位符指向的数据库配置信息放在bean中定义的工具。
下面来看一个通过 PropertyPlaceholderConfigurer读取加解密配置文件的案例:
1 package com.xttblog.plugin; 2 import com.zheng.common.util.AESUtil; 3 import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer; 4 //支持加密配置文件插件 5 public class EncryptPropertyPlaceholderConfigurer extends PropertyPlaceholderConfigurer { 6 private String[] propertyNames = { 7 "master.jdbc.password", "slave.jdbc.password", "generator.jdbc.password", "master.redis.password" 8 }; 9 //解密指定propertyName的加密属性值 10 @Override 11 protected String convertProperty(String propertyName, String propertyValue) { 12 for (String p : propertyNames) { 13 if (p.equalsIgnoreCase(propertyName)) { 14 return AESUtil.AESDecode(propertyValue); 15 } 16 } 17 return super.convertProperty(propertyName, propertyValue); 18 } 19 }
我们只需重写 PropertyPlaceholderConfigurer 类的 convertProperty 方法即可,然后在该方法中实现解密工作。