最近网站更新的有点频繁,以前这类发布的事情都有运维专人来搞定, 现在什么都自己弄,很多地方就觉得太麻烦了,

就拿这个项目发布来说,每次把项目拉到服务器上,都要修改一下数据库以及其它 一些 如静态目录等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系统加载的属性。

你可能感兴趣的内容
Spring 之 JMS 监听JMS消息 收藏,4406 浏览
Spring 之 JMS 基于JMS的RPC 收藏,3253 浏览
Spring的BeanFactory和FactoryBean 收藏,10405 浏览
0条评论

selfly

交流QQ群:32261424
Owner