进行数据源或者 FTP 服务器等资源配置时,我们可以将这些配置信息放到一个独立的外部属性文件中,并在 Spring 配置文件中通过形如 ${user}
、${password}
的占位符方式来引用属性文件中的属性项 。
这种方式的配置有两个好处:
- 减少了维护的工作量 - 资源的配置信息可以被多个应用共享,如果资源的配置信息发生了变更,那么我们只需要调整这个独立的配置文件就可以啦。
- 部署更加简单 - 通过一个独立的属性文件来存放这些配置信息,则部属人员只需要调整这个属性文件即可,无须关注结构复杂、信息量大的 Spring 配置文件咯。
Spring 提供了一个 PropertyPlaceholderConfigurer ,它能够在装载 Bean 时引用外部属性文件 。PropertyPlaceholderConfigurer 实现了 BeanFactoryPostProcessorBean 接口,所以它是一个 Bean 工厂后处理器。
1 基本引用
1.1 PropertyPlaceholderConfigurer 方式(XML 配置)
假设需要在 Bean 中定义一个数据源:
这里把驱动类名 、JDBC 的 URL 以及数据库的用户名和密码都直接写在了 XML 中 。 这样在部署时,如果需要改动数据库的配置信息,那么首先需要先找到这个 XML,然后再进行修改,不方便 。
建议将这些信息抽取到一个配置文件中,假设取名为 system.priperties:
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/spring4
username=root
password=
属性文件可以定义多个属性,每个属性的格式为:
属性名=属性值
spring 配置:
通过这样配置之后,我们在部署时,仅需关注这个配置文件即可。
PropertyPlaceholderConfigurer 属性说明如下:
属性 | 说明 |
---|---|
location | 指定属性文件的路径。 |
locations | 指定多个属性文件的路径。 |
fileEncoding | 文件的编码格式,如果不指定,那么 Spring 会使用操作系统的默认编码格式来读取文件的内容。 |
order | 如果配置文件中定义了多个 PropertyPlaceholderConfigurer ,那么可以通过这个属性来指定优先顺序。 |
placeholderPrefix | 占位符后缀,默认为 ${ 。 |
placeholderSuffix | 占位符前缀,默认为 } 。 |
1.2 context:property-placehoder 方式(XML 配置)
可以使用 context 命名空间来定义属性文件,相对于 PropertyPlaceholderConfigurer 的配置方式,这种方式更优雅。
这种方式虽然如果希望对属性进行加密或者使用数据库表保存配置信息等的高级功能,就必须扩展 PropertyPlaceholderConfigurer 的类,然后采用之前所说的 Bean 配置方式 。
1.3 @value 方式(基于注解或 JAVA 类的配置)
基于注解的 Bean 可以通过 @Value 注解为 Bean 的成员变量或者方法入参自动注入属性值 。
@Component
public class CustomDataSource {
@Value("${driverClassName}")
private String driverClassName;
@Value("${url}")
private String url;
@Value("${username}")
private String username;
@Value("${password}")
private String password;
//省略 getter/setter
}
而基于 JAVA 类注解 @Configuration 的类本身就标注了 @Component,所以它引用属性的方式和基于注解配置的引用方式是一样的 。
注意:使用过程中要确保所引用的属性值在属性文件中存在并且类型匹配,否则会抛出异常。
2 加密属性值
对于那些不敏感的属性信息,在属性文件中以明文形式出现是合理的,但是如果属性信息是敏感信息(比如数据库用户名和密码等),建议以密文的方式保存 。 因为如果敏感信息密文保存,那么任何拥有服务器登陆权限的人就有可能看到机密信息,从而影响系统安全。
对于那些对安全要求特别高的系统(银行、公安系统等),这些敏感信息应该只掌握在少数特定的维护人员手里。所以,我们需要对这些信息进行加密,Spring 容器读取文件后,再对其进行解密。
PropertyPlaceholderConfigurer 继承自 PlaceholderConfigurerSupport 类,后者设计了一些方法,用于在属性被使用之前对它们进行转换:
方法 | 说明 |
---|---|
convertProperty(String propertyName, String propertyValue) | 加载并读取每一个属性值时,都会调用此方法对其进行转换处理。 |
String convertPropertyValue(String originalValue) | 与前一个方法功能相似,只不过参数只传入了属性值。 |
void convertProperties(Properties props) | 转换所有的属性值。 |
默认情况下,这三个都是空方法,我们可以扩展 PropertyPlaceholderConfigurer ,覆盖相应的转换方法,从而支持带加密后的属性值文件。
2.1 DES 加密解密工具类
信息的加密分为对称和非对称两种方式, 对称表示加密后的信息可以解密出来,而非对称方式则不能根据加密后的信息解密为原值 。 MD5 属于非对称加密, DES 属于对称加密 。所以这里我们使用 DES 加密属性值;读取属性值时,再使用 DES 进行解密 。
DES 加密解密工具类源代码请点击这里。
DES 加解密使用说明:
我们使用 DES 加解密工具,通过命令行的方式,对数据库的账号与密码进行加密;接着,把加密后的字符串写入 system.properties,形如:
username=q5L+2PPrsPQ=
password=UdyjsvkXc/Q=
2.2 对属性文件的值进行加密
首先自定义属性配置器,支持解密转换:
public class CustomPropertyPlaceholderConfigurer extends PropertyPlaceholderConfigurer {
/**
* DES 密钥
*/
private static final String KEY_STR = "123456";
/**
* 值加密过的属性名称组
*/
public static final String[] ENCRYPT_PROPERTY_NAMES = new String[]{"username", "password"};
@Override
protected String convertProperty(String propertyName, String propertyValue) {
if (!isDecrypt(propertyName)) {
return propertyValue;
}
//解密
return new DES(KEY_STR).decrypt(propertyValue);
}
/**
* 是否需要解密
*
* @param propertyName 属性名称
* @return
*/
private boolean isDecrypt(String propertyName) {
return ArrayUtils.contains(ENCRYPT_PROPERTY_NAMES, propertyName);
}
}
注意:
- 这里的 DES 密钥必须与之前加密的密钥相同。
- ArrayUtils 来源于 commons-lang3。
然后通过
的方式来配置自定义的属性文件:
这样,Spring 容器就可以加载被加密过的属性文件啦,是不是很简单呀
O(∩_∩)O哈哈~
3 对自身的引用
Spring 既允许在 Bean 定义中通过 ${propName}
引用属性的值,也允许在属性文件中使用 ${propName}
实现属性之间的相互引用 。
database=spring4
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/${database}
这里通过 ${database}
引用了另外一个属性的值(数据库实例名)。因此,对于一些复杂的属性,我们可以通过这种方式将属性变化的部分抽取出来,从而实现配置的最小化 。
注意:如果一个属性值太长,我们可以在每一行的最后加上 “\
”,就可以把属性值划分为多行,就像这样:
profile.jdbc.url=jdbc:mysql://127.0.0.1:3306/dbName?useUnicode=true&characterEncoding\
=UTF-8\
&zeroDateTimeBehavior=convertToNull