打造易于部署的WEB应用项目
程序员文章站
2022-04-15 19:38:22
...
概述
很多WEB应用的配置文件位于项目的类路径下(如WEB-INF\classes\conf\conf.properties),数据源也直接采用DBCP,C3P0等数据源,在Spring配置文件中定义。这种项目结构的部署性存在很大问题,其一,部署人员需要到WAR包中去更改配置信息,其二,新版本的WAR包不能直接覆盖生产环境下的WAR包,否则原有配置文件会被覆盖。也即这个WAR包是有状态的,它既包含了程序代码还包含了生产环境的配置信息,因此配置性,便携性将大大弱化。本文将给出一种优化的项目结构,解决项目部署性问题。
"传统的"WEB项目属性文件
以下是一个传统的WEB项目,在其Spring配置文件中通过<context:property-placeholder/>引用一个基于类路径的属性文件,如下所示:
这个WEB项目打成WAR包后,在生产环境下如果要更改conf.properties配置文件中的参数,必须在WAR包中更改,虽然大部分WEB应用都会解开WAR到一个文件目录。即使如此,对于开发人员及实施人员都是头疼的。
对于开发人员来说,每次打WAR包时需要使用生产环境的conf.properties替换开发环境的conf.properties,而往往开发人员并不清楚生产环境的具体情况,此时,开发人员只能请求实施人员不要用这个WAR包直接覆盖生产环境下的WAR包。
对于实施人员来说,他得战战兢兢地升级WAR包,先将生产环境下WAR包的conf.properties拷贝出来,覆盖升级版的WAR包中的conf.properties,以免生产环境的conf.properties被覆盖了。当然,平常要更改配置参数也很麻烦,需要到每个WAR包中去更改。
便于部署的WEB项目如何规划属性文件
为了方便WEB项目的部署,必须将属性文件和WAR包解耦,即WAR包中不包含配置文件。但这样问题又来了,难不成开发环境下,需要将配置文件放到一个项目之外的目录下,那项目的团队开发岂不是又很麻烦,需要每个成员都手工拷贝项目的配置文件,而无法使用SVN同步即可。
为了使项目既具有开发便携性(SVN一同步,mvn jetty:run即可启动项目),又具有部署便携性(上传覆盖升级的WAR包即可,无需其它动作)。我们只需要通过一个系统参数定义配置文件的位置即可完美满足开发便携性和部署便携性了。
在①处,我们用一个JVM系统参数指定配置文件的路径,此处是开发环境,我们将配置文件放在项目的类路径下,可通过项目的SVN同步,项目团队所有成员都可共享之。
上面的Spring配置文件调整下,通过这个系统参数CONFFILE_PATH引用属性配置文件
在开发环境下,使用Maven的Jetty插件,Jetty插件指定的CONFFILE_PATH系统参数为类路径下的conf/conf.properties。
在部署环境下,我们在WEB应用服务器中定义JVM的系统参数,拿Websphere来说,定义JVM系统参数如下所示:
在此,我们将配置文件放在file:/D:/framework/conf/framework.properties文件中。
(注:对于Tomcat,Jetty都可在应用服务器的配置文件中定义JVM参数,大家请自行网上找下)
这样,部署时直接覆盖旧版的项目WAR包即可,由于配置文件已经放在独立于WAR包的外部操作系统的文件目录下,就不用考虑WAR升级对配置文件的影响了。
数据源的配置
WEB应用基本上都有数据源,传统的WEB项目很多采用DBCP,C3P0等数据源在Spring配置文件中定义,这样会给部署带来问题:
因此,应该让Spring引用JNDI的数据源,而不应该在Spring配置文件中通过DBCP,C3P0自行定义数据源。
当然,一个很现实的问题是,你如何在开发环境下使用JNDI数据源,其实这已经不是问题了,Jetty很早就可以定义JNDI数据源了,并不是只有Websphere,Weblogic才能定义JNDI数据源。
下面是使用Maven的maven-jetty-plugin插件定义JNDI数据源的配置:
在①处添加了一个额外的jetty配置文件jetty.xml,其配置内容如下:
这里定义了一个名为jdbc/myds的JNDI数据源,这样项目的Spring配置文件就可直接引用这个JNDI数据源了:
由于JNDI的数据源名称不是固定的,比如在部署环境下,可能就叫"jdbc/yourds"了,因此需要将jndi的名字属性化。结合上面我们所说的配置文件,可以在配置文件中添加一个属性,暂且我们将该属性命名为ds.jndi:
Spring配置文件中通过参数名引用JNDI的数据源:
Prefect!至此,开发人员和部署人员都皆大欢喜了,部署环境下定义好JNDI数据源后,各WAR包即可利用之。
是不是发现Maven和Spring真的无所不能呢?
很多WEB应用的配置文件位于项目的类路径下(如WEB-INF\classes\conf\conf.properties),数据源也直接采用DBCP,C3P0等数据源,在Spring配置文件中定义。这种项目结构的部署性存在很大问题,其一,部署人员需要到WAR包中去更改配置信息,其二,新版本的WAR包不能直接覆盖生产环境下的WAR包,否则原有配置文件会被覆盖。也即这个WAR包是有状态的,它既包含了程序代码还包含了生产环境的配置信息,因此配置性,便携性将大大弱化。本文将给出一种优化的项目结构,解决项目部署性问题。
"传统的"WEB项目属性文件
以下是一个传统的WEB项目,在其Spring配置文件中通过<context:property-placeholder/>引用一个基于类路径的属性文件,如下所示:
<context:property-placeholder location="classpath:conf/conf.properties"/> <rop:annotation-driven core-pool-size="${rop.corePoolSize}" max-pool-size="${rop.maxPoolSize}" queue-capacity="${rop.queueCapacity}" keep-alive-seconds="${rop.keepAliveSeconds}" thread-ferry-class="${rop.threadFerryClass}" sign-enable="${rop.signEnable}" ext-error-base-names="${rop.customErrorBasenames}" service-timeout-seconds="${rop.serviceTimeoutSeconds}" session-manager="sessionManager" app-secret-manager="appSecretManager" formatting-conversion-service="conversionService"/>
这个WEB项目打成WAR包后,在生产环境下如果要更改conf.properties配置文件中的参数,必须在WAR包中更改,虽然大部分WEB应用都会解开WAR到一个文件目录。即使如此,对于开发人员及实施人员都是头疼的。
对于开发人员来说,每次打WAR包时需要使用生产环境的conf.properties替换开发环境的conf.properties,而往往开发人员并不清楚生产环境的具体情况,此时,开发人员只能请求实施人员不要用这个WAR包直接覆盖生产环境下的WAR包。
对于实施人员来说,他得战战兢兢地升级WAR包,先将生产环境下WAR包的conf.properties拷贝出来,覆盖升级版的WAR包中的conf.properties,以免生产环境的conf.properties被覆盖了。当然,平常要更改配置参数也很麻烦,需要到每个WAR包中去更改。
便于部署的WEB项目如何规划属性文件
为了方便WEB项目的部署,必须将属性文件和WAR包解耦,即WAR包中不包含配置文件。但这样问题又来了,难不成开发环境下,需要将配置文件放到一个项目之外的目录下,那项目的团队开发岂不是又很麻烦,需要每个成员都手工拷贝项目的配置文件,而无法使用SVN同步即可。
为了使项目既具有开发便携性(SVN一同步,mvn jetty:run即可启动项目),又具有部署便携性(上传覆盖升级的WAR包即可,无需其它动作)。我们只需要通过一个系统参数定义配置文件的位置即可完美满足开发便携性和部署便携性了。
<plugin> <groupId>org.mortbay.jetty</groupId> <artifactId>maven-jetty-plugin</artifactId> <version>6.1.5</version> <configuration> <webDefaultXml>src/resources/dev/webdefault.xml</webDefaultXml> <systemProperties> <!-- ①注意这里,我们用一个JVM系统属性指定配置文件的路径 --> <systemProperty> <name>CONFFILE_PATH</name> <value>classpath:conf/conf.properties</value> </systemProperty> </systemProperties> <webAppSourceDirectory>src/main/webapp</webAppSourceDirectory> <scanIntervalSeconds>0</scanIntervalSeconds> <contextPath>/</contextPath> <connectors> <connector implementation="org.mortbay.jetty.nio.SelectChannelConnector"> <port>7100</port> </connector> </connectors> </configuration> </plugin>
在①处,我们用一个JVM系统参数指定配置文件的路径,此处是开发环境,我们将配置文件放在项目的类路径下,可通过项目的SVN同步,项目团队所有成员都可共享之。
上面的Spring配置文件调整下,通过这个系统参数CONFFILE_PATH引用属性配置文件
<!-- ① 注意这儿,通过SpringEL表示式访问系统参数的值 --> <context:property-placeholder location="#{systemProperties.CONFFILE_PATH}"/> <rop:annotation-driven core-pool-size="${rop.corePoolSize}" max-pool-size="${rop.maxPoolSize}" queue-capacity="${rop.queueCapacity}" keep-alive-seconds="${rop.keepAliveSeconds}" thread-ferry-class="${rop.threadFerryClass}" sign-enable="${rop.signEnable}" ext-error-base-names="${rop.customErrorBasenames}" service-timeout-seconds="${rop.serviceTimeoutSeconds}" session-manager="sessionManager" app-secret-manager="appSecretManager" formatting-conversion-service="conversionService"/>
在开发环境下,使用Maven的Jetty插件,Jetty插件指定的CONFFILE_PATH系统参数为类路径下的conf/conf.properties。
在部署环境下,我们在WEB应用服务器中定义JVM的系统参数,拿Websphere来说,定义JVM系统参数如下所示:
引用
操作步骤:
服务器->应用程序服务器(选择对应的应用程序服务器)->服务器基础结构(Java 和进程管理/进程定义)->其它属性(Java虚拟机)->其他属性(定制属性)
服务器->应用程序服务器(选择对应的应用程序服务器)->服务器基础结构(Java 和进程管理/进程定义)->其它属性(Java虚拟机)->其他属性(定制属性)
在此,我们将配置文件放在file:/D:/framework/conf/framework.properties文件中。
(注:对于Tomcat,Jetty都可在应用服务器的配置文件中定义JVM参数,大家请自行网上找下)
这样,部署时直接覆盖旧版的项目WAR包即可,由于配置文件已经放在独立于WAR包的外部操作系统的文件目录下,就不用考虑WAR升级对配置文件的影响了。
数据源的配置
WEB应用基本上都有数据源,传统的WEB项目很多采用DBCP,C3P0等数据源在Spring配置文件中定义,这样会给部署带来问题:
- 无法共享数据源,如果一个Server中存在多个WAR项目,这些WAR项目都访问同一个数据库时,由于在各自项目中独立配置数据源,会造成有多少个项目就有多少个数据源的问题,无法实现数据源的共享。
- 数据源的优化不方便,如果通过Spring配置文件中配置DBCP,C3P0等数据源,数据源的优化参数抽取到配置文件中。由于数据源优化参数很多,往往并不是每个参数都抽取配置文件中,使数据源的优化受限。
因此,应该让Spring引用JNDI的数据源,而不应该在Spring配置文件中通过DBCP,C3P0自行定义数据源。
当然,一个很现实的问题是,你如何在开发环境下使用JNDI数据源,其实这已经不是问题了,Jetty很早就可以定义JNDI数据源了,并不是只有Websphere,Weblogic才能定义JNDI数据源。
下面是使用Maven的maven-jetty-plugin插件定义JNDI数据源的配置:
<plugin> <groupId>org.mortbay.jetty</groupId> <artifactId>maven-jetty-plugin</artifactId> <version>6.1.5</version> <configuration> <webDefaultXml>src/resources/dev/webdefault.xml</webDefaultXml> <!--①添加一个jetty的额外配置文件 --> <jettyEnvXml>src/resources/jetty.xml</jettyEnvXml> <systemProperties> <systemProperty> <name>CONFFILE_PATH</name> <value>classpath:conf/conf.properties</value> </systemProperty> </systemProperties> <webAppSourceDirectory>src/main/webapp</webAppSourceDirectory> <scanIntervalSeconds>0</scanIntervalSeconds> <contextPath>/</contextPath> <connectors> <connector implementation="org.mortbay.jetty.nio.SelectChannelConnector"> <port>7100</port> </connector> </connectors> </configuration> </plugin>
在①处添加了一个额外的jetty配置文件jetty.xml,其配置内容如下:
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://jetty.mortbay.org/configure.dtd"> <Configure class="org.mortbay.jetty.webapp.WebAppContext"> <New id="myds" class="org.mortbay.jetty.plus.naming.Resource"> <Arg>jdbc/myds</Arg> <Arg> <New class="org.apache.commons.dbcp.BasicDataSource"> <Set name="driverClassName">oracle.jdbc.driver.OracleDriver</Set> <Set name="url">jdbc:oracle:thin:@127.0.0.1:1521:orcl</Set> <Set name="username">test</Set> <Set name="password">test</Set> </New> </Arg> </New> </Configure>
这里定义了一个名为jdbc/myds的JNDI数据源,这样项目的Spring配置文件就可直接引用这个JNDI数据源了:
<?xml version="1.0" encoding="UTF-8" ?> <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" xmlns:p="http://www.springframework.org/schema/p" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:jee="http://www.springframework.org/schema/jee" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:task="http://www.springframework.org/schema/task" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.2.xsd"> ... <jee:jndi-lookup id="frameworkDS" jndi-name="java:comp/env/jdbc/myds"/> </beans>
由于JNDI的数据源名称不是固定的,比如在部署环境下,可能就叫"jdbc/yourds"了,因此需要将jndi的名字属性化。结合上面我们所说的配置文件,可以在配置文件中添加一个属性,暂且我们将该属性命名为ds.jndi:
conf/conf.properties ds.jndi=java:comp/env/jdbc/yourds
Spring配置文件中通过参数名引用JNDI的数据源:
<?xml version="1.0" encoding="UTF-8" ?> <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" xmlns:p="http://www.springframework.org/schema/p" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:jee="http://www.springframework.org/schema/jee" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:task="http://www.springframework.org/schema/task" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.2.xsd"> ... <context:property-placeholder location="#{systemProperties.CONFFILE_PATH}"/> <jee:jndi-lookup id="frameworkDS" jndi-name="${ds.jndi}"/> </beans>
Prefect!至此,开发人员和部署人员都皆大欢喜了,部署环境下定义好JNDI数据源后,各WAR包即可利用之。
是不是发现Maven和Spring真的无所不能呢?
上一篇: 关于JAR包版本冲突的几个应对招数总结