在实际工作中,我们创建的应用大部分是web应用。在Java的世界中,Web项目的标准打包方式是WAR。这一章,我们介绍怎么使用Maven构建一个Web应用,此外我们还会介绍如何借助jetty-maven-plugin来快速开发和测试Web测试,以及使用Cargo实现Web项目的自动化部署。
Web项目的目录结构
基于Java的Web应用,其标准的打包方式是WAR。WAR与JAR类似,不过他可以包含更多的内容,如JSP文件、Servlet、Java类、Web.xml配置文件、依赖JAR包、静态web资源(如HTML、CSS、JavaScript文件)等。一个典型的war文件会有如下目录结构:
-war/
+META-INF/
+WEB-INF/
| + classes/
| | + ServletA.class
| | + config.properties
| | + ...
| |
| + lib/
| | + dom4j-1.4.1.jar
| | + mail-1.4.1.jar
| | + ...
| |
| + web.xml
|
+ img/
|
+css/
|
+ js/
|
+ index.html
+ sample.jsp
一个war包下至少包含两个子项目:META-INF和WEB-INF。前者包含了一些打包元数据信息,一般不用关心,而后者是War包的核心。WEB-INF下必须包含一个Web资源描述文件web.xml,它的子目录classes包含所有该Web项目的类,而另一个子目录lib则包含所有该Web项目的依赖JAR包,classes和lib目录都会在运行时被加入到Classpath中。除了META-INF和WEB-INF外,一般的WAR包都会包含很多Web资源。
Maven对于Web项目的打包方式必须显示指定为war,否则无法正确打包。如:
<project>
...
<groupId>com.javenxu.mvnbook</goupId>
<artifactId>sample-war</artifactId>
<packaging>war</packaging>
<version>1.0-SNAPSHOT</version>
...
</project>
一个典型的Web项目的Maven目录结构如下:
+ project
|
+ pom.xml
|
+ src /
+ main /
| + java /
| | + ServletA.java
| | + ...
| |
| + resources /
| | + ...
| |
| + webapp /
| + WEB-INF /
| | + web.xml
| |
| + img /
| |
| + css /
| |
| + js /
| +
| +index.html
| +sample.jsp
|
+ test /
| + java /
| + resources /
在src/main/webapp/目录下,必须包含一个子目录WEB-INF,该子目录还必须要包含web.xml文件。src/main/webapp目录下的其他文件和目录包括html、jsp、css、JavaScript等,它们与WAR包中的Web资源完全一致。
<finalName>:
在一些Web项目中,大家可能会看到finalName元素的配置。该元素用来标识项目生成的主构件的名称,该元素的默认值已在超级POM中设定,值为${project.artifactId} - ${project.version},不过,这样的名称太过于冗长,我们都不想在访问页面的时候输入冗长的地址,因此我们会需要名字更为简洁的war包。例,这时可以如下所示配置finalName元素:
<finalName>account</finalName>
经此配置后,项目生成的war包名称就会成为account.war,更方便部署。
使用jetty-maven-plugin进行测试:
传统的Web测试方法要求我们编译、测试、打包及部署,这往往会消耗数10秒至数分钟的时间,jetty-maven-plugin能够帮助我们节省时间,它能够周期性地检查项目内容,发现变更后自动更新到内置的Jetty Web容器中。换句话说,它帮我们省去了打包和部署的步骤。jetty-maven-plugin默认很好的支持了Maven的项目目录结构。在通常情况下,我们只需要直接在IDE中修改源码,IDE能够执行自动编译,jetty-maven-plugin发现编译后的文件变化后,自动将其更新到Jetty容器,这时就可以直接测试Web页面了。
使用jetty-maven-plugin十分简单。指定该插件的坐标,并且稍加配置即可,代码如下:
<plugin>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>7.1.6.v20100715</version>
<configuration>
<scanIntervalSeconds>10</scanIntervalSeconds>
<webAppConfig>
<contextPath>/test</contextPath>
</webAppConfig>
</configuration>
</plugin>
在该插件的配置中,scanIntervalSeconds顾名思义表示该插件扫描项目变更的时间间隔,这里的配置是每隔10秒。需要注意的是,如果不进行配置,该元素的默认值是0,表示不扫描,用户也就失去了所谓的自动化热部署的功能。上述代码中webappConfig元素下的contextPath表示项目部署后的context path。例如这里的值为/test,那么用户就可以通过http://hostname:port/test访问该应用。
下一步启动jetty-maven-plugin。在这之前需要对settings.xml做个微小的修改。前面介绍过,默认情况下,只有org.apache.maven.plugins和org.codehaus.mojo两个groupId下的插件才支持简化的命令行调用,即可以运行mvn help: system,但mvn jetty:run就不行了。因为jetty-maven-plugin的groupId是org.mortbay,jetty,为了能在命令行直接运行mvn jetty:run,用户需要配置setting.xml如下:
<settings>
<pluginGroups>
<pluginGroup>org.mortbay.jetty</pluginGroup>
</pluginGroups>
...
</settings>
现在可以运行如下命令启动jetty-maven-plugin:
$mvn jetty:run
jetty-maven-plugin会启动Jetty,并且默认监听本地的8080端口,并将当前项目部署到容器中,同时还会根据用户配置扫描代码改动。
如果希望使用其他端口,可以添加jetty.port参数。例如:
$mvn jetty:run -Djetty.port=9999
现在就可以打开http://localhost:9999/test/测试应用了。要停止Jetty,只需要在命令行输入Ctrl+C即可。
启动Jetty后,用户可以在IDE中修改各类文件。只要不是修改类名、方法名等较大的操作,jetty-maven-plugin都能够扫描到变更并正确地将变化更新至Web容器中。
使用Cargo实现自动化部署:
部署至本地Web容器:
Cargo能帮助用户在Web容器上实现自动化部署Web项目,它支持几乎所有的Web容器。Cargo通过cargo-maven2-plugin提供了Maven集成。虽然cargo-maven2-plugin和jetty-maven-plugin的功能看起来很相似,但它们的目的是不同的,jetty-maven-plugin主要用来帮助日常的快速开发和测试,而cargo-maven2主要服务于自动化部署。
Cargo支持两种本地部署的方式,分别为standalone模式和existing模式。在standalone模式中,Cargo会从Web容器的安装目录复制一份配置到用户指定的目录,然后在此基础上部署应用,每次重新构建的时候,这个目录都会被清空,所有配置被重新生成。而在existing模式中,用户需要指定现有的Web容器配置目录,然后Cargo会直接使用这些配置并将部署到其相应的位置。
下面代码展示了standalone模式的配置样例。
<plugin>
<groupId>org.codehaus.cargo</groupId>
<artifactId>cargo-maven2-plugin</artifactId>
<version>1.0</version>
<configuration>
<container>
<containerId>tomcat6x</containerId>
<home>D:\cmd\apache-tomcat-6.0.29</home>
</container>
<configuration>
<type>standalone</type>
<home>${project.build.directory}/tomcat6x</home>
<properties>
<cargo.servlet.port>8081</cargo.servlet.port>
</properties>
</configuration>
</configuration>
</plugin>
默认情况下,Cargo会让Web容器监听8080端口,上面我们修改了Cargo的cargo.servlet.port属性来改变这一配置。
cargo-maven2-plugin的groupId是org.codehaus.cargo,这不属于官方的两个Maven插件groupId,因此用户需要将其添加到setting.xml的pluginGroup元素中以方便命令行调用。
现在,要让Cargo启动Tomcat并部署应用,只需要运行:
$mvn cargo:start
然后我们就可以访问该web项目的页面了。
要将应用直接部署到现有的Web容器下,需要配置Cargo使用existing模式,代码如下:
<plugin>
<groupId>org.codehaus.cargo</groupId>
<artifactId>cargo-maven2-plugin</artifactId>
<version>1.0</version>
<configuration>
<container>
<containerId>tomcat6x</containerId>
<home>D:\cmd\apache-tomcat-6.0.29</home>
</container>
<configuration>
<type>existing</type>
<home>D:\cmd\apache-tomcat-6.0.29</home>
</configuration>
</configuration>
</plugin>
部署至远程Web容器:
除了让Cargo直接管理本地Web容器然后部署应用之外,也可以让Cargo部署应用至远程的正在运行的Web容器中。前提是,拥有该容器的相应管理员权限。
<plugin>
<groupId>org.codehaus.cargo</groupId>
<artifactId>cargo-maven2-plugin</artifactId>
<version>1.0</version>
<configuration>
<container>
<containerId>tomcat6x</containerId>
<home>remote</home>
</container>
<configuration>
<type>runtime</type>
<home>${project.build.directory}/tomcat6x</home>
<properties>
<cargo.remote.username>8081</cargo.remote.username>
<cargo.remote.password>admin123</cargo.remote.password>
<cargo.tomcat.manager.url>http://localhost:8080/manager</cargo.tomcat.manager.url>
</properties>
</configuration>
</configuration>
</plugin>
对于远程部署的方式来说,container元素的type子元素的值必须为remote。configuration的type子元素值为runtime,表示既不使用独立的容器配置,也不使用本地现有的容器配置,而是依赖于一个已运行的容器。properties元素用来声明一些容器热部署相关的配置。例如,这里的Tomcat6就需要提供用户名、密码以及管理地址。需要注意的是,这部分配置对于所有容器来说都是不一致的,读者需要查阅对应的Cargo文档。
有了上述配置之后,就可以让Cargo部署应用了。运行命令如下:
$mvn cargo:redeploy