大型项目中,我们往往会对我们的系统的配置信息进行统一管理,一般做法是将配置信息配置与一个cfg.properties的文件中,然后在我们系统初始化的时候,系统自动读取cfg.properties配置文件中的key value(键值对),然后对我们系统进行定制的初始化。
Spring中提供着一个PropertyPlaceholderConfigurer
这个类是BeanFactoryPostProcessor的子类。(不懂自己度娘,可了解可不了解,下面我会讲大概)
其主要的原理在是。Spring容器初始化的时候,会读取xml或者annotation对Bean进行初始化。初始化的时候,这个PropertyPlaceholderConfigurer会拦截Bean的初始化,初始化的时候会对配置的${pname}进行替换,根据我们Properties中配置的进行替换。从而实现表达式的替换操作 。
PropertyPlaceholderConfigurer是个bean工厂后置处理器的实现,也就是 BeanFactoryPostProcessor接口的一个实现。
PropertyPlaceholderConfigurer如果在指定的Properties文件中找不到你想使用的属性,它还会在Java的System类属性中查找。
(未验证真实性,摘自PropertyPlaceholderConfigurer读取配置文件 )
我们可以通过System.setProperty(key, value)或者java中通过-Dnamevalue来给Spring配置文件传递参数。
了解完原理之后,我们来看其实现的几种方式。
在我们的classpath下新建一个cfg.properties
#cfg.properties配置文件的内容
username=jay
password=123
下面是Spring配置文件代码
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<!-- 对于读取一个配置文件采取的方案 -->
<property name="location" value="classpath:cfg.properties"></property>
<!-- 对于读取两个以上配置文件采取的处理方案 -->
<!--
<property name="locations">
<list>
<value>classpath:cfg.properties</value>
<value>classpath:cfg2.properties</value>
</list>
</property>
-->
</bean>
</beans>
//测试代码
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationCtx.xml")
public class SpringCtxTst {
@Value("${username}")
private String uname;
@Value("${password}")
private String pwd;
@Test
public void test(){
System.out.println("username:"+uname);
System.out.println("password:"+pwd);
}
}
控制台输出
username:jay
password:123
当然,Spring 为此也为我们提供了另外一个的解决方案,我们直接在Spring 配置文件中书写如下代码即可实现
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd">
<!--采用这种方式简化配置文件-->
<context:property-placeholder location="classpath:cfg.properties,classpath:cfg2.properties"/>
</beans>
- 注意
我们知道,不论是使用PropertyPlaceholderConfigurer还是通过context:property-placeholder这种方式进行实现,都需要记住,Spring框架不仅仅会读取我们的配置文件中的键值对,而且还会读取Jvm 初始化的一下系统的信息。有时候,我们需要将配置Key定一套命名规则 ,例如
项目名称.组名.功能名=配置值
org.team.tfunction=0001
这种方式来尽量减少与系统配置信息的冲突!
同时,我们也可以使用下面这种配置方式进行配置,这里我配NEVER的意思是不读取系统配置信息。如果
<context:property-placeholder location="classpath:cfg.properties,classpath:cfg2.properties" system-properties-mode="NEVER"/>
继承PropertyPlaceholderConfigurer 实现动态加载配置文件
在项目开发的过程中,往往有多个环境,dev,test,uat,run等.各个环境的配置文件是不同的.改造spring的PropertyPlaceholderConfigurer可以实现为各个不同的环境读取到各自的配置文件.这样为项目的部署省去很多工作.
Spring 的配置文件中,PropertyPlaceholderConfigurer 是占位符配置.为PropertyPlaceholderConfigurer 配置 .properties文件后,在Spring的配置文件中就可以使用${}的方式获取.properties数据.假设现有dev,test,uat,run四个环境,在每个项目web容器启动参数里面添加不同启动参数,下图以dev为例
创建动态加载配置文件的DynamicServerConfig.java文件,核心方法为loadProperties,如下
package com.iris.spring.config;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.core.io.Resource;
import org.springframework.util.DefaultPropertiesPersister;
import org.springframework.util.PropertiesPersister;
/**
*
* @author xiami
*
*/
public class DynamicServerConfig extends PropertyPlaceholderConfigurer {
/** Logger available to subclasses */
protected final Log logger = LogFactory.getLog(getClass());
protected Properties[] localProperties;
protected boolean localOverride = false;
private Resource[] locations;
private PropertiesPersister propertiesPersister = new DefaultPropertiesPersister();
/**
* copy
*/
public void setLocations(Resource[] locations) {
this.locations = locations;
}
/**
* copy
*/
public void setLocalOverride(boolean localOverride) {
this.localOverride = localOverride;
}
/**
* 根据启动参数,动态读取配置文件
*/
protected void loadProperties(Properties props) throws IOException {
if (locations != null) {
logger.info("Start loading properties file.....");
for (Resource location : this.locations) {
InputStream is = null;
try {
String filename = location.getFilename();
String env = "system." + System.getProperty("spring.profiles.active", "run") + ".properties";
if (filename.contains(env)) {
logger.info("Loading properties file from " + location);
is = location.getInputStream();
this.propertiesPersister.load(props, is);
}
} catch (IOException ex) {
logger.info("读取配置文件失败.....");
ex.printStackTrace();
throw ex;
} finally {
if (is != null) {
is.close();
}
}
}
}
}
}
在Spring配置文件中applicationContext.xml中配置DynamicServerConfig.
<bean id="PropertyPlaceholderConfigurer" class="com.iris.spring.config.DynamicServerConfig">
<property name="locations">
<list>
<value>classpath:config/system.development.properties</value>
<value>classpath:config/system.run.properties</value>
<value>classpath:config/system.test.properties</value>
<value>classpath:config/system.uat.properties</value>
</list>
</property>
</bean>
这样在tomcat启动的时候,Spring就会读取system.development.properties文件中的信息,不会读取其他几个文件中的信息.
上述的做法就是在applicationContext.xml中的DynamicServerConfig.的bean节点的list配置所有环境的properties文件名,
在jvm启动参数中配置当前想要连的环境的properties文件名(或文件名的unique部分),读取jvm启动参数,从而顺藤摸瓜读取相应环境的properties文件。
转载自:继承PropertyPlaceholderConfigurer 实现动态加载配置文件
怎么直接使用PropertyPlaceholderConfigurer 看下文
applicationContext.xml中的使用${}是代表什么意思?
一般情况下,如果你只有一个applicationContext.xml配置文件而已的话,那么只需要在applicationContext.xml文件中添加一行:
<!-- 导入外部的properties文件 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
其中:location属性是指该文件的位置。
如果是在src目录下的话,该位置为:classpath:文件名.后缀
如果是在/WEB-INF/目录下的话,该位置为: /WEB-INF/文件名.后缀。
但是要注意,不要放错位置了,<context:property-placeholder>不能发到<bean></bean>标签里面去。否则会报错;
PropertyPlaceholderConfigurer源码解析
前言
我们在开发中常遇到一种场景,Bean里面有一些参数是比较固定的,这种时候通常会采用配置的方式,将这些参数配置在.properties文件中,然后在Bean实例化的时候通过Spring将这些.properties文件中配置的参数使用占位符"${...}"替换的方式读入并设置到Bean的相应参数中。
这种做法最典型的就是JDBC的配置,本文就来研究一下.properties文件读取及占位符"${}"替换的源码,首先从代码入手,定义一个DataSource,模拟一下JDBC四个参数:
1 public class DataSource { 2 3 /** 4 * 驱动类 5 */ 6 private String driveClass; 7 8 /** 9 * jdbc地址 10 */ 11 private String url; 12 13 /** 14 * 用户名 15 */ 16 private String userName; 17 18 /** 19 * 密码 20 */ 21 private String password; 22 23 public String getDriveClass() { 24 return driveClass; 25 } 26 27 public void setDriveClass(String driveClass) { 28 this.driveClass = driveClass; 29 } 30 31 public String getUrl() { 32 return url; 33 } 34 35 public void setUrl(String url) { 36 this.url = url; 37 } 38 39 public String getUserName() { 40 return userName; 41 } 42 43 public void setUserName(String userName) { 44 this.userName = userName; 45 } 46 47 public String getPassword() { 48 return password; 49 } 50 51 public void setPassword(String password) { 52 this.password = password; 53 } 54 55 @Override 56 public String toString() { 57 return "DataSource [driveClass=" + driveClass + ", url=" + url + ", userName=" + userName + ", password=" + password + "]"; 58 } 59 60 }
定义一个db.properties文件:
1 driveClass=0 2 url=1 3 userName=2 4 password=3
定义一个properties.xml文件:
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 xmlns:aop="http://www.springframework.org/schema/aop" 5 xmlns:tx="http://www.springframework.org/schema/tx" 6 xsi:schemaLocation="http://www.springframework.org/schema/beans 7 http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 8 http://www.springframework.org/schema/aop 9 http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> 10 11 <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> 12 <property name="location" value="properties/db.properties"></property> 13 </bean> 14 15 <bean id="dataSource" class="org.xrq.spring.action.properties.DataSource"> 16 <property name="driveClass" value="${driveClass}" /> 17 <property name="url" value="${url}" /> 18 <property name="userName" value="${userName}" /> 19 <property name="password" value="${password}" /> 20 </bean> 21 22 </beans>
写一段测试代码:
1 public class TestProperties { 2 3 @Test 4 public void testProperties() { 5 ApplicationContext ac = new ClassPathXmlApplicationContext("spring/properties.xml"); 6 7 DataSource dataSource = (DataSource)ac.getBean("dataSource"); 8 System.out.println(dataSource); 9 } 10 11 }
运行结果就不贴了,很明显,下面就来分析一下Spring是如何将properties文件中的属性读入并替换"${}"占位符的。
PropertyPlaceholderConfigurer类解析
在properties.xml文件中我们看到了一个类PropertyPlaceholderConfigurer,顾名思义它就是一个属性占位符配置器,看一下这个类的继承关系图:
看到从这张图上,我们能分析出来的最重要的一点就是PropertyPlaceholderConfigurer是BeanFactoryPostProcessor接口的实现类,想见Spring上下文必然是在Bean定义全部加载完毕后且Bean实例化之前通过postProcessBeanFactory方法一次性地替换了占位符"${}"。