一、背景
在软件开发和部署过程中,我们的软件往往需要在不同的运行环境中运行,例如:开发人员本地开发环境、测试团队的测试环境、生产仿真环境、正式生产环境,不同的公司可能还会有更多的环境需要对项目配置进行动态切换。项目在这些环境切换的过程中,往往会有一部分配置是所有的环境都相同的,还有一部分是不同环境都不相同的(最典型的就是数据连接配置文件jdbc.properties),如果我们不做特殊配置,那么我们就需要根据环境来修改配置文件,不同环境来回修改和切换,不仅容易出错,而且很繁琐,那么这时候我们在想:有没有办法可以能够让我们不用修改配置就能发布到不同的环境中呢?答案显而易见,那么本文我们就通过三种方式来解决这个问题。从而把我们的软件的可移植性提高一个层次。
二、软件环境
Spring 4.2.6.RELEASE
SpringMvc 4.2.6.RELEASE
Mybatis 3.2.8
Maven 3.3.9
Jdk 1.7
Idea 15.04
首先我们在spring-dao.xml有如下数据源定义:
<!-- 配置数据源,数据库连接池 --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close"> <!--数据库驱动--> <property name="driverClassName" value="${jdbc.driver}"/> <!--数据库链接地址--> <property name="url" value="${jdbc.url}"/> <!--数据库用户名--> <property name="username" value="${jdbc.username}"/> <!--数据库连接密码--> <property name="password" value="${jdbc.password}"/> <!--连接池初始化大小--> <property name="initialSize" value="${jdbc.initialSize}"/> <!--连接池最小数量--> <property name="minIdle" value="${jdbc.minIdle}" /> <!--连接池最大数量--> <property name="maxActive" value="${jdbc.maxActive}"/> <!--连接池等待超时时间--> <property name="maxWait" value="${jdbc.maxWait}"/> <!--配置间隔多久才进行一次检测,检测需要关闭空闲连接,单位是毫秒--> <property name="timeBetweenEvictionRunsMillis" value="${jdbc.timeBetweenEvictionRunsMillis}"/> <property name="minEvictableIdleTimeMillis" value="${jdbc.minEvictableIdleTimeMillis}"/> <property name="testWhileIdle" value="${jdbc.testWhileIdle}"/> <property name="validationQuery" value="${jdbc.validationQuery}"/> <property name="testOnReturn" value="${jdbc.testOnReturn}"/> <!--打开PSCache,并且制定每个连接上PSCache的大小--> <property name="poolPreparedStatements" value="${jdbc.poolPreparedStatements}"/> <property name="maxPoolPreparedStatementPerConnectionSize" value="${jdbc.maxPoolPreparedStatementPerConnectionSize}"/> <!--配置监控统计拦截的filters--> <property name="filters" value="${jdbc.filters}"/> </bean>
然后,我们在classpath的config文件夹下定义:config-development.properties、config-test.properties、config-emulation.properties、config-production.properties四个分别对应本地开发环境、测试环境、仿真环境、生产环境的配置文件,它们中分别指定着不同的配置参数,如数据库连接url、数据库用户名、数据库密码等.
三、方式一:使用spring的profile机制实现
1.在spring.xml中的数据源配置之前定义不同的spring的profile
<!-- 开发环境配置 --> <beans profile="dev"> <context:property-placeholder location="classpath:config/config-development.properties"/> </beans> <!-- 测试环境配置 --> <beans profile="test"> <context:property-placeholder location="classpath:config/config-test.properties"/> </beans> <!-- 仿真环境配置 --> <beans profile="emu"> <context:property-placeholder location="classpath:config/config-emulation.properties"/> </beans> <!-- 生产环境配置 --> <beans profile="prod"> <context:property-placeholder location="classpath:config/config-production.properties"/> </beans> <!--其他和环境无关的配置--> <beans> ..... </beans>
然后spring.xml中和环境无关的配置用一个不设置profile的beans标签包裹。
2.定义默认生效的profile,也就是当我们没有主动激活任何profile的情况下,该配置会生效.
1).web.xml中进行如下设置
<!-- 配置spring的默认profile --> <context-param> <param-name>spring.profiles.default</param-name> <param-value>dev</param-value> </context-param>
2).在spring-dao.xml指定默认profile
<beans profile="default"> <context:property-placeholder location="classpath:config/config-development.properties"/> </beans>
3.多环境切换方式
spring为我们提供了大量的激活profile的方式:代码激活、系统环境变量激活、JVM参数激活、Servlet上下文参数激动等。我们一般用的最多的就是使用JVM参数进行激活,简单方便。以tomcat为例,我们只需要在tomcat的启动脚本中加入以下JVM参数:-Dspring.profiles.active=xxx (其中xxx为我们在spring-dao.xml中定义的profile的名称:dev、test、emu、prod),在不同的环境中我们指定该参数的值为环境对应的profile名称即可。很灵活,代码也不需要做出任何改变。
4.使用扩展
spring的profile还允许我们在java代码或者jsp的el表达式中来根据该参数做不同的操作。如:
public void test() { //侦测jvm环境 String env = System.getProperty("spring.profiles.active"); if(env == "dev") { do xxx; } else { do other things; } }
在jsp的el表达式中使用,比如我们只需要在生产环境添加统计流量的代码等:
<!-- 生产环境统计、推送代码 --> <c:if test="${spring.profiles.active == 'prod' }"> <script> //统计代码 .. </script> </c:if>
四、方式二:使用maven的profile机制实现
1.在项目的pom.xml中定义maven profiles
<profiles> <profile> <!-- 本地开发环境 --> <id>dev</id> <properties> <environment>development</environment> </properties> <activation> <activeByDefault>true</activeByDefault> </activation> </profile> <profile> <!-- 测试环境 --> <id>test</id> <properties> <environment>test</environment> </properties> </profile> <profile> <!-- 仿真环境 --> <id>emu</id> <properties> <environment>emulation</environment> </properties> </profile> <profile> <!-- 生产环境 --> <id>prod</id> <properties> <environment>production</environment> </properties> </profile> </profiles>
这里我们定义了四个环境,分别为:development(开发环境)、test(测试环境)、emulation(仿真环境)、production(生产环境),其中开发环境模式是激活的(activeByDefault为true),这样如果我们在不指定profile的情况下默认是开发环境。
2.在spring-dao.xml中进行如下配置:
<context:property-placeholder location="classpath:config/config-${environment}.properties"/>
3.不同环境构建或者打包项目
所有的上述步骤昨晚以后,见证奇迹的时候到了,我们只需要在运行maven命令的时候指定使用不同的profile名称就可以实现构建或发布到不同环境需要的war包,如:
mvn clean package -Pprod 即构建出生产环境需要的war包
mvn clean install -Ptest 即构建要发布到测试环境的war包
五、方式三:使用maven的profile + resources + filter实现
1.首先和第二种方式一样在pom.xml中进行如下配置各个环境对应的profiles
<profiles> <profile> <id>dev</id> <properties> <environment>development</environment> </properties> <activation> <activeByDefault>true</activeByDefault> </activation> </profile> <profile> <id>test</id> <properties> <environment>test</environment> </properties> </profile> <profile> <id>emu</id> <properties> <environment>emulation</environment> </properties> </profile> <profile> <id>prod</id> <properties> <environment>production</environment> </properties> </profile> </profiles>
2.接着在pom.xml中定义resources中要被过滤的资源以及过滤时用到的资源文件
<build> <resources> <resource> <directory>src/main/resources</directory> <excludes> <exclude>config/*</exclude> </excludes> <filtering>true</filtering> </resource> <resource> <directory>src/main/resources</directory> <includes> <include>config/config-${environment}.properties</include> </includes> <filtering>false</filtering> </resource> </resources> <filters> <filter>src/main/resources/config/config-${environment}.properties</filter> </filters> </build>
注:上面配置中第一个resource定义了排除config文件夹下的所有文件,并且对src/main/resources文件夹进行过滤替换占位符,使用的配置文件是红色部分标出的文件,第二个resource定义了将config文件夹下config-${environment}.properties文件作为资源文件,并且不对该文件做过滤。
3.不同环境构建或者打包项目
我们只需要在运行maven命令的时候指定使用不同的profile名称就可以实现构建或发布到不同环境需要的war包,如:
mvn clean package -Pprod 即构建出生产环境需要的war包
mvn clean install -Ptest 即构建要发布到测试环境的war包
项目github地址:https://github.com/hafizzhang/maven-profile.git
六、总结
三种方式都可以很完美的实现多环境打包部署,但方式一依赖spring,方式二和方式三依赖maven。第二种方式是运行时替换占位符,可以使用JVM参数来替换配置文件内容,第三种方式是打包时替换占位符,不支持运行时通过JVM参数替换配置文件内容。我个人更倾向于和推荐使用第二种方式,它灵活多变,可扩展性高!