Spring之PropertyPlaceholderConfigurer加载配置文件

PropertyPlaceholderConfigurer加载配置文件

    1.是什么
    2.工作场景
    3.使用方法
        工作场景1的使用方法
        工作场景2的使用方法
        代码中获取配置属性
    4.源码分析
        1)加载配置文件到Properties中
        2)转化Properties进行重新提交,重写该方法可以对配置项进行处理,例如解密
        3)把Properties加载到spring容器中替换BeanDefinition中${}对应的属性

1.是什么

PropertyPlaceholderConfigurer是spring中bean的后置处理器的实现类,也就是BeanFactoryPostProcessor接口的一个实现类
2.工作场景

工程中经常会把配置项放在一个或者多个Properties文件中,项目启动时加载Properties文件到Properties中,然后进行替换Spring容器中BeanDefinition对应属性中${}
当我们配置项中会涉及到数据库密码之类的敏感数据,我们需要加密放置在配置文件中,该场景可以继承PropertyPlaceholderConfigurer方法重写convertProperty方法,在Spring启动加载配置文件到Properties后进行解密,然后进行替换Spring容器中BeanDefinition对应属性中${}

3.使用方法
工作场景1的使用方法

工作场景1的方法比较简单,流程如下,以数据配置文件为例:

编写jdbc.properties文件

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/data?serverTimezone=UTC
jdbc.username=root
jdbc.password=123456

1
2
3
4

配置PropertyPlaceholderConfigurer加载文件
在Spring配置文件applicationContext.xml中配置,随Spring启动进行加载

<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="ignoreResourceNotFound" value="true" />
    <property name="locations">
        <list>
            <value>classpath:config/jdbc.properties</value>
        </list>
    </property>
</bean>

1
2
3
4
5
6
7
8

如果引用多个文件有两种形式
形式1

<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="ignoreResourceNotFound" value="true" />
    <property name="locations">
        <list>
            <value>classpath:config/*.properties</value>
        </list>
    </property>
</bean>

1
2
3
4
5
6
7
8

形式2

classpath:config/jdbc.properties classpath:conf.properties
1
2
3
4
5
6
7
8

配置项使用场景有两种使用方法
1)配置文件中使用

<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
    <property name="driverClassName" value="${jdbc.driver}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>

1
2
3
4
5
6

2)在代码中通过@Value使用,例如

@Value("${jdbc.username}")
private String username;

1
2

工作场景2的使用方法

当文件中存在加密配置时,这些配置项并不能直接使用,需要进行解密后才能使用

编写jdbc.properties文件

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/data?serverTimezone=UTC
jdbc.username=root
jdbc.password=mJephukdo4Js82iZSmCu25mSBSO+J5Rs0li8taFtbCs=

1
2
3
4

2.自定义ReadPropertyCfg类继承PropertyPlaceholderConfigurer,重写convertProperty方法

public class ReadPropertyCfg extends PropertyPlaceholderConfigurer implements InitializingBean {
private static Map<String, String> ctxPropertiesMap =
new HashMap<String, String>(16);
/**
* 需要做解密处理的key
/
private static Set encryptSet = new HashSet(16);
static {
// 需要做加解密的key
encryptSet.add(“jdbc.password”);
}
/
*
* 配置文件中加密配置属性的转换
*
* @param propertyName 配置的名称
* @param propertyValue 配置的值
* @return 转化结果
/
@Override
protected String convertProperty(String propertyName, String propertyValue) {
if (null != propertyName && (encryptSet.contains(propertyName))) {
String pwdresult = AESCryptor.decryptData(propertyValue, CipherConfig.getWorkCipherKey(“mysql.workkey”));
return pwdresult;
}
return propertyValue;
}
/
*
* 在初始化根秘钥
*
* @throws IOException
* @throws DecoderException
*/
@Override
public void afterPropertiesSet() throws SecureUtilCommonException, DecoderException, IOException {
setFileEncoding(“UTF-8”);
CipherConfig.init();
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

对上述代码进行简要分析,encryptSet是放置所有需要解密的属性,convertProperty方法把需要解密的数据进行解密,如何加载到Spring容器中等下分析
3. 在Spring配置文件applicationContext.xml中配置ReadPropertyCfg来加载配置文件

<bean id="propertyConfigurer" class="com.huawei.alliance.commons.config.ReadPropertyCfg">
    <property name="ignoreResourceNotFound" value="true" />
    <property name="locations">
        <list>
            <value>classpath:config/*.properties</value>
        </list>
    </property>
</bean>

1
2
3
4
5
6
7
8

配置项使用场景有两种使用方法
1)配置文件中使用

<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
    <property name="driverClassName" value="${jdbc.driver}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>

1
2
3
4
5
6

2)在代码中通过@Value使用,例如

@Value("${jdbc.password}")
private String password;

1
2

注:spring容器中最多只能定义一个context:property-placeholder,否则会报错:Could not resolve placeholder XXX,但如果想引入多个属性文件怎么办那,可以使用通配符:<context:property-placeholder location=“classpath:*.properties”/>
代码中获取配置属性

1.需要自定义ReadPropertyCfg类继承PropertyPlaceholderConfigurer,重写processProperties方法,通过map来进行承接配置项

public class ReadPropertyCfg extends PropertyPlaceholderConfigurer implements InitializingBean {
private static Map<String, String> ctxPropertiesMap =
new HashMap<String, String>(16);
/**
* 通过重写processProperties方法把Properties属性写入到Map中,然后供后续使用
/
@Override
protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, Properties props)
throws BeansException {
super.processProperties(beanFactoryToProcess, props);
String keyStr = “”;
String value = “”;
for (Object key : props.keySet()) {
keyStr = key.toString();
if (null != keyStr && null != props.getProperty(keyStr)) {
value = props.getProperty(keyStr);
ctxPropertiesMap.put(keyStr, value);
}
}
}
/
*
* 通过获取map中的属性的key获取对应的value
*/
public static String getContextProperty(String name) {
return ctxPropertiesMap.get(name);
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

4.源码分析

那现在疑问就来了,我们自定义PropertyPlaceholderConfigurer或者直接使用PropertyPlaceholderConfigurer,Spring是如何加载配置项的哪?
我们通过自定义的ReadPropertyCfg 反向进行分析Properties加载过程

从反向可以推导出最终是AbstractApplicationContext中的refresh触发的,具体的在这不进行黏贴,可根据上述调用关系在Spring源码中进行查看即可。
下面对重要的代码进行重点分析

PropertyResourceConfigurer.java
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
try {
//1)加载配置文件到Properties中
Properties mergedProps = mergeProperties();

		//2)转化Properties进行重新提交,重写该方法可以对配置项进行处理,例如解密
		convertProperties(mergedProps);

		//3)把Properties加载到spring容器中替换BeanDefinition中${}对应的属性
		processProperties(beanFactory, mergedProps);
	}
	catch (IOException ex) {
		throw new BeanInitializationException("Could not load properties", ex);
	}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

由上述代码可知配置文件从加载到最终加载Spring容器中,可分为三步
1)加载配置文件到Properties中

/**
 * Return a merged Properties instance containing both the
 * loaded properties and properties set on this FactoryBean.
 */
protected Properties mergeProperties() throws IOException {
	Properties result = new Properties();

	if (this.localOverride) {
		// Load properties from file upfront, to let local properties override.
		loadProperties(result);
	}

	if (this.localProperties != null) {
		for (Properties localProp : this.localProperties) {
			CollectionUtils.mergePropertiesIntoMap(localProp, result);
		}
	}

	if (!this.localOverride) {
		// Load properties from file afterwards, to let those properties override.
		loadProperties(result);
	}

	return result;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

2)转化Properties进行重新提交,重写该方法可以对配置项进行处理,例如解密

PropertyResourceConfigurer的convertProperties方法

protected void convertProperties(Properties props) {
	Enumeration<?> propertyNames = props.propertyNames();
	while (propertyNames.hasMoreElements()) {
		String propertyName = (String) propertyNames.nextElement();
		String propertyValue = props.getProperty(propertyName);
		String convertedValue = convertProperty(propertyName, propertyValue);
		if (!ObjectUtils.nullSafeEquals(propertyValue, convertedValue)) {
			props.setProperty(propertyName, convertedValue);
		}
	}
}

1
2
3
4
5
6
7
8
9
10
11

在上述代码中如果没有重写convertProperty方法,propertyValue不发生变化,还是获取原始配置,源码如下

protected String convertProperty(String propertyName, String propertyValue) {
	return convertPropertyValue(propertyValue);
}
protected String convertPropertyValue(String originalValue) {
	return originalValue;
}

1
2
3
4
5
6

重写convertProperty方法

@Override
protected String convertProperty(String propertyName, String propertyValue) {
    if (null != propertyName && (encryptSet.contains(propertyName))) {
        String pwdresult = AESCryptor.decryptData(propertyValue, CipherConfig.getWorkCipherKey("mysql.workkey"));
        return pwdresult;
    }
    return propertyValue;
}

1
2
3
4
5
6
7
8

在执行convertProperty代码之前Properties已经有了所有的配置项,只不过是配置项中原生的,重写convertProperty就是为了把需要解密的数据进行解析,把需要解密的数据解密后进行重新设置Properties的对应属性达到解密的目的
3)把Properties加载到spring容器中替换BeanDefinition中${}对应的属性

代码或者配置文件已经被加载Spring容器中对应BeanDefinition的属性,只不过现在为止还是以${}的形式存在,processProperties的过程就是把BeanDefinition的对应属性替换为对应Properties中解密后的value

/**
 * Visit each bean definition in the given bean factory and attempt to replace ${...} property
 * placeholders with values from the given properties.
 */
@Override
protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, Properties props)
		throws BeansException {

	StringValueResolver valueResolver = new PlaceholderResolvingStringValueResolver(props);
	doProcessProperties(beanFactoryToProcess, valueResolver);
}
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值