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:mysql://127.0.0.1:3306/data?serverTimezone=UTC
=root
=123456
1
2
3
4
配置PropertyPlaceholderConfigurer加载文件
在Spring配置文件中配置,随Spring启动进行加载
<bean class="">
<property name="ignoreResourceNotFound" value="true" />
<property name="locations">
<list>
<value>classpath:config/</value>
</list>
</property>
</bean>
1
2
3
4
5
6
7
8
如果引用多个文件有两种形式
形式1
<bean class="">
<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/ classpath:1
2
3
4
5
6
7
8
配置项使用场景有两种使用方法
1)配置文件中使用
<bean class=".">
<property name="driverClassName" value="${}"/>
<property name="url" value="${}"/>
<property name="username" value="${}"/>
<property name="password" value="${}"/>
</bean>
1
2
3
4
5
6
2)在代码中通过@Value使用,例如
@Value("${}")
private String username;
1
2
工作场景2的使用方法
当文件中存在加密配置时,这些配置项并不能直接使用,需要进行解密后才能使用
编写文件
=
=jdbc:mysql://127.0.0.1:3306/data?serverTimezone=UTC
=root
=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
(“”);
}
/*
* 配置文件中加密配置属性的转换
*
* @param propertyName 配置的名称
* @param propertyValue 配置的值
* @return 转化结果
/
@Override
protected String convertProperty(String propertyName, String propertyValue) {
if (null != propertyName && ((propertyName))) {
String pwdresult = (propertyValue, (“”));
return pwdresult;
}
return propertyValue;
}
/*
* 在初始化根秘钥
*
* @throws IOException
* @throws DecoderException
*/
@Override
public void afterPropertiesSet() throws SecureUtilCommonException, DecoderException, IOException {
setFileEncoding(“UTF-8”);
();
}
}
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配置文件中配置ReadPropertyCfg来加载配置文件
<bean class="">
<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 class=".">
<property name="driverClassName" value="${}"/>
<property name="url" value="${}"/>
<property name="username" value="${}"/>
<property name="password" value="${}"/>
</bean>
1
2
3
4
5
6
2)在代码中通过@Value使用,例如
@Value("${}")
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 {
(beanFactoryToProcess, props);
String keyStr = “”;
String value = “”;
for (Object key : ()) {
keyStr = ();
if (null != keyStr && null != (keyStr)) {
value = (keyStr);
(keyStr, value);
}
}
}
/*
* 通过获取map中的属性的key获取对应的value
*/
public static String getContextProperty(String name) {
return (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源码中进行查看即可。
下面对重要的代码进行重点分析
@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 () {
// Load properties from file upfront, to let local properties override.
loadProperties(result);
}
if ( != null) {
for (Properties localProp : ) {
(localProp, result);
}
}
if (!) {
// 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 = ();
while (()) {
String propertyName = (String) ();
String propertyValue = (propertyName);
String convertedValue = convertProperty(propertyName, propertyValue);
if (!(propertyValue, convertedValue)) {
(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 && ((propertyName))) {
String pwdresult = (propertyValue, (""));
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);
}