最近网站更新的有点频繁,以前这类发布的事情都有运维专人来搞定, 现在什么都自己弄,很多地方就觉得太麻烦了,
就拿这个项目发布来说,每次把项目拉到服务器上,都要修改一下数据库以及其它 一些 如静态目录等properties的配置文件,就觉得很烦,终于在这两天受不了了,决定构建一下让项目在多环境加载不同配置文件。
项目是maven项目,当然使用maven可以搞定,请看这里:[ 使用maven profile实现多环境可移植构建 ][ maven_profie ],
但是我始终觉得那样太麻烦。项目是使用spring的想着能不能简单点直接让spring加载不同的配置文件呢?
一查还真有,原来spring早就提供了,实现起来也非常简单。
主要有两种方式可以达到我们的目的。
使用多个 PropertyPlaceholderConfigurer
原先项目是下面这样加载配置文件的,使用了spring提供的快捷声明方式,一行代码搞定:
<!-- 多个配置文件可用,号分隔 --> <context:property-placeholder location="classpath:mysqlDS.properties"/>
加载classpath下的mysqlDS.properties
文件,很方便,它实际等同于:
<bean id="propertyPlaceholderConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations"> <list> <value>classpath:mysqlDS.properties</value> </list> </property> </bean>
在正式部署时需要修改该配置文件中的数据库配置信息,这里我们可以变通一下来达到我们的目的。
因为spring支持声明多个 propertyPlaceholderConfigurer,而先加载的 propertyPlaceholderConfigurer中的属性不会被后面的同名属性所覆盖,基于这个原理我们声明两个 propertyPlaceholderConfigurer,如下:
<bean id="propertyConfigurer1" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="order" value="1"/> <property name="ignoreResourceNotFound" value="true"/> <property name="ignoreUnresolvablePlaceholders" value="true" /> <property name="location" value="file:${catalina.home}/conf/mysqlDS.properties"/> </bean> <bean id="propertyConfigurer2" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="order" value="2"/> <property name="ignoreUnresolvablePlaceholders" value="true" /> <property name="location" value="classpath:mysqlDS.properties"/> </bean>
第一个配置加载tomcat目录conf下的 mysqlDS.properties,注意这里使用了file:
,${catalina.home}
只要tomcat启动它就会存在。
第二个配置加载classpath下的 mysqlDS.properties,这个跟上面一样。
其中order属性代表其加载的顺序,如果没有则按照在xml中的声明顺序。
ignoreUnresolvablePlaceholders是否忽略不可解析的Placeholder,这里必须设为true,否则在你的开发环境tomcat下没有放置 mysqlDS.properties就抛出异常了。
假设两个配置文件一模一样,它们的解析规则如下:
1 propertyConfigurer1加载配置文件后,它里面的属性并不会被后面的 propertyConfigurer2所覆盖,这时 propertyConfigurer2是失效的。
2 tomcat下的 mysqlDS.properties不存在时, propertyConfigurer1失效,这时 propertyConfigurer2将加载配置文件。
3 多个 propertyConfigurer不会相互覆盖,但 如果同一个 propertyConfigurer中加载了多个配置文件,则后面的会覆盖前面先加载的的。
4 这里指的是属性相同的情况,如果属性不同则不会有任何冲突都将被加载。
基于上面的原理,我们只需要在正式的tomcat的conf目录下放一个 mysqlDS.properties就能达到我们的目的了,即简单又方便。
在实际使用时碰上了个奇怪的问题,就是怎么都连接不上数据库,报用户名密码错误,但是我的配置明明是对的,只使用一个 propertyConfigurer时也是可以的。后来通过调试发现,我的数据库用户名居然变成了liyd,这是我操作系统的用户,被加载进去了,根据多个 propertyConfigurer 后面不会覆盖前面的原则导致了数据库用户名错误。这应该是属性和系统中的重名了,将配置文件中的username=xxxxx改成dbuser=xxxxx问题解决
实现自定义的 PropertyPlaceholderConfigurer
这里我采用了上面的第一种方式,第二种就不细讲了,简单的贴一下代码,一看也就能明白。
public class CustomPropertyPlaceholderConfigurer extends PropertyPlaceholderConfigurer { public void setCustomPropFiles(Set<String> customPropFiles) { //这里假定配置文件都在tomcat的conf目录 String tomcatHome = System.getProperty("catalina.home") + "/conf"; String fileSeparator = System.getProperty("file.separator"); Properties properties = new Properties(); for (String customPropFile : customPropFiles) { // 如develop、test、release 这里根据实际需要来确定配置文件名称,可以通过环境变量等方式,结合实际情况 String propName = "test"; customPropFile = StringUtils.replace(customPropFile, "${propName}", propName); String file = tomcatHome + fileSeparator + customPropFile; try { Properties prop = new Properties(); //实际应用中这里最好判断一下文件是否存在 prop.load(new FileInputStream(new File(file))); properties.putAll(prop); } catch (Exception e) { logger.error("加载配置文件失败:" + file, e); throw new RuntimeException("加载配置文件失败"); } } //关键方法,通过这个方法将自定义加载的properties文件加入spring中 this.setProperties(properties); } }
在xml配置文件中声明:
<bean id="customPropertyPlaceholderConfigurer" class="com.dexcoder.spring.ext.CustomPropertyPlaceholderConfigurer"> <property name="customPropFiles"> <set> <value>${propName}_ds.properties</value> <value>${propName}_mq.properties</value> </set> </property> </bean>
这样也就达到我们加载自定义配置文件的目的了,要注意的是这个 customPropertyPlaceholderConfigurer声明尽量靠前,要在spring执行 mergeProperties方法之前。
spring实际上就是调用org.springframework.core.io.support.PropertiesLoaderSupport#mergeProperties来完成配置文件的合并的,源码如下:
/** * 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; }
注意localOverride的值,为true的话同名的用户属性将覆盖spring系统加载的属性。