项目管理和构建工具
Maven是Apache开源组织提供的一个基于POM(Project Object Model)的项目构建(Build)工具,所谓的构建指的是代码清除(clean)、编译(compile)、测试(test)、打包(package)、部署(deploy)等一系列流程。
pom 项目对象模型
项目构建(Build):搭建项目环境的过程
代码清除(clean):删除编译后的class文件
自动管理依赖
dependency 依赖 就是jar包的意思
自动管理依赖关系 jar包和jar包 之间的关系
struts-core.jar -----> xwork-core.jar 依赖关系
A ----> B -----> C ----> D 要使用A必须导入BCD jar包
在使用Maven的时候 只需要导入A jar地址 Maven会自动的帮你下载A的所有依赖jar包
Maven会通过依赖坐标自动下载你需要的jar包 和相关的依赖jar
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
maven通过install将本地工程打包成jar包,放入到本地仓库中,再通过pom.xml配置依赖引入到当前工程。
pom.xml中引入的坐标首先在本地maven仓库中查找,若没有则去maven的网上中央仓库查找,并放到本地仓库供项目使用。
那么如果我们是内部项目,自定义的构建并不公开至网络上,项目成员又想依赖他怎么办呢?想想maven找寻构建的步骤。
先找寻本地仓库,本地仓库不存在,找寻远程仓库或者私服。
我们只需把自定义的构建安装至私服或者本地仓库中就行了。这就需要maven的install命令。
maven远程仓库地址 https://mvnrepository.com/
src
main
java 源代码目录
resources 配置文件目录
test 测试包
测试java 源代码目录
resources 测试配置文件
pom.xml 核心配置文件 写jar包的坐标
Maven的生命周期(lifecycle)是为了
对所有的构建过程进行 抽象和统一
,其实际行为都由插件来完成。
生命周期包含了项目的清理,初始化,编译,打包,集成测试,验证,部署和站点生成等几乎所有步骤。可以理解成由各种plugin按照一定的顺序执行来完成java项目清理、编译、打包、测试、布署等整个项目的流程的一个过程。
Maven拥有
三套相互独立的生命周期
,它们分别为clean、default和site
。
clean
生命周期的目的是清理项目
,
default
生命周期的目的是构建项目
,
site
生命周期的目的是建立项目站点
。
每个生命周期包含一些阶段(phase),这些阶段是有顺序的,并且后面的阶段依赖于前面的阶段
,用户和Maven最直接的交互方式就是调用这些生命周期阶段。
以clean生命周期为例,它包含的阶段有pre-clean、clean和post-clean。当用户调用pre-clean的时候,只有pre-clean阶段得以执行;当用户调用clean的时候,pre-clean和clean阶段会得以顺序执行;当用户调用post-clean的时候,pre-clean、clean和post-clean会得以顺序执行。
每个生命周期阶段的前后存在依赖关系,但是三套生命周期本身是相互独立的
,用户可以仅仅调用clean生命周期的某个阶段,或者仅仅调用default生命周期的某个阶段,而不会对其他生命周期产生任何影响。
1)
pre-clean
执行一些清理前需要完成的工作。
2)clean
清理上一次构建生成的文件。
3)post-clean
执行一些清理后需要完成的工作。
default生命周期
定义了真正构建时所需要执行的所有步骤
,它是所有生命周期中最核心的部分,其包含的阶段如下:
·validate
·initialize
·generate-sources
·process-sources处理项目主资源文件。一般来说,是对src/main/resources目录的内容进行变量替换等工作后,复制到项目输出的主classpath目录中。
·generate-resources
·process-resources
·compile编译项目的主源码
。一般来说,是编译src/main/java目录下的Java文件至项目输出的主classpath目录中。
·process-classes
·generate-test-sources
·process-test-sources处理项目测试资源文件。一般来说,是对src/test/resources目录的内容进行变量替换等工作后,复制到项目输出的测试classpath目录中。
·generate-test-resources
·process-test-resources
·test-compile编译项目的测试代码
。一般来说,是编译src/test/java目录下的Java文件至项目输出的测试classpath目录中。
·process-test-classes
·test使用单元测试框架运行测试,测试代码不会被打包或部署。
·prepare-package
·package接受编译好的代码,打包成可发布的格式,如JAR
。
·pre-integration-test
·integration-test
·post-integration-test
·verify
·install将包安装到Maven本地仓库,供本地其他Maven项目使用
。
·deploy将最终的包复制到远程仓库,供其他开发人员和Maven项目使用
。
对于上述未加解释的阶段,读者也应该能够根据名字大概猜到其用途,若想了解进一步的这些阶段的详细信息,可以参阅官方的解释:http://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html。
site生命周期的目的是建立和发布项目站点,Maven能够基于POM所包含的信息,自动生成一个友好的站点,方便团队交流和发布项目信息。
·pre-site执行一些在生成项目站点之前需要完成的工作。
·site生成项目站点文档。
·post-site执行一些在生成项目站点之后需要完成的工作。
·site-deploy将生成的项目站点发布到服务器上。
从命令行执行Maven任务的最主要方式就是调用Maven的生命周期阶段。
·$
mvn clean
:该命令调用clean生命周期的clean阶段
。实际执行的阶段为clean生命周期的pre-clean和clean阶段。
·$mvn test
:该命令调用default生命周期的test阶段
。实际执行的阶段为default生命周期的validate、initialize等,直到test的所有阶段。这也解释了为什么在执行测试的时候,项目的代码能够自动得以编译。
·$mvn clean install
:该命令调用clean生命周期的clean阶段和default生命周期的install阶段
。实际执行的阶段为clean生命周期的pre-clean、clean阶段,以及default生命周期的从validate至install的所有阶段。该命令结合了两个生命周期,在执行真正的项目构建之前清理项目是一个很好的实践
。
·$mvn clean deploy site-deploy
:该命令调用clean生命周期的clean阶段、default生命周期的deploy阶段,以及site生命周期的site-deploy阶段
。实际执行的阶段为clean生命周期的pre-clean、clean阶段,default生命周期的所有阶段,以及site生命周期的所有阶段。该命令结合了Maven所有三个生命周期,且deploy为default生命周期的最后一个阶段,site-deploy为site生命周期的最后一个阶段。
Maven的生命周期与插件相互绑定,用以完成实际的构建任务
。
例如项目编译这一任务,它对应了default生命周期的compile这一阶段,而maven-compiler-plugin这一插件的compile目标能够完成该任务。如图所示:
当用户通过命令行调用生命周期阶段的时候,对应的插件目标就会执行相应的任务。
很多插件目标的参数都支持从命令行配置,用户可以在Maven命令中使用-D参数,并伴随一个参数键=参数值的形式,来配置插件目标的参数。
例如,maven-surefire-plugin提供了一个maven.test.skip参数,当其值为true的时候,就会跳过执行测试。于是,在运行命令的时候,加上如下-D参数就能跳过测试:
mvn install -Dmaven.test.skip=true
参数-D是Java自带的,其功能是通过命令行设置一个Java系统属性,Maven简单地重用了该参数,在准备插件的时候检查系统属性,便实现了插件参数的配置。
基本上所有主要的Maven插件都来自Apache和Codehaus。由于Maven本身是属于Apache软件基金会的,因此它有很多官方的插件,每天都有成千上万的Maven用户在使用这些插件,它们具有非常好的稳定性。详细的列表可以在这个地址得到:http://maven.apache.org/plugins/index.html,单击某个插件的链接便可以得到进一步的信息。所有官方插件能在这里下载:http://repo1.maven.org/maven2/org/apache/maven/plugins/。
plugin:
生命周期(lifecycle)由各个阶段组成,每个阶段由maven的插件plugin来执行完成。生命周期(lifecycle)主要包括clean、resources、complie、install、package、testResources、testCompile、deploy等,其中带test开头的都是用于编译测试代码或运行单元测试用例的。
各个插件的执行顺序一般是:1.clean->2.resources->3.compile->4.testResources->5.testCompile->6.test->7.jar->8.install。
maven内置的各种插件,如果pom中没有配置就调用默认的内置插件,如果pom中配置了就调用配置的插件
maven的构建过程:
由各种插件按照一定的顺序执行来完成项目的编译,单元测试、打包、布署的完成
clean插件
maven-clean-plugin
resources插件
maven-resources-plugin
compile插件
maven-compiler-plugin
单元测试插件
maven-surefire-plugin
打包插件
maven-jar-plugin、maven-assembly-plugin、maven-shade-plugin 3种
maven-jar-plugin:可执行jar与依赖包是分开的,需要建立lib目录里来存放需要的j依赖包,且需要jar和lib目录在同级目录
maven-assembly-plugin:这个插件可以把所有的依赖包打入到可执行jar包。但是该插件有个bug会缺失spring的xds文件,导致无法运行jar,同时如果同级目录还有其它可执行jar文件依赖可能会产生冲突。
maven-shade-plugin:所有的依赖包打入到可执行jar包,如果同级目录有其它可执行jar,依赖可能会产生冲突,且运行jar时,有时会出现SF、DSA、RSA文件冲突的提示,需要排除META-INF目录下的文件。
本地发布插件
maven-install-plugin
发布插件的功能就是把构建好的artifact部署到本地仓库
远程发布插件
maven-deploy-plugin
将构建好的artifact部署到远程仓库。
maven命令package、install、deploy的联系与区别
mvn clean package依次执行了clean、resources、compile、testResources、testCompile、test、jar(打包)等7个阶段。
mvn clean install依次执行了clean、resources、compile、testResources、testCompile、test、jar(打包)、install等8个阶段。
mvn clean deploy依次执行了clean、resources、compile、testResources、testCompile、test、jar(打包)、install、deploy等9个阶段。
区别:
package命令完成了项目编译、单元测试、打包功能,但没有把打好的可执行jar包(war包或其它形式的包)布署到本地maven仓库和远程maven私服仓库。
install命令完成了项目编译、单元测试、打包功能,同时把打好的可执行jar包(war包或其它形式的包)布署到本地maven仓库,但没有布署到远程maven私服仓库。
deploy命令完成了项目编译、单元测试、打包功能,同时把打好的可执行jar包(war包或其它形式的包)布署到本地maven仓库和远程maven私服仓库。
Maven的
聚合特性
能够把项目的各个模块聚合在一起构建
,
而Maven的继承特性
则能帮助抽取各模块相同的依赖和插件等配置
,在简化POM的同时,还能促进各个模块配置的一致性
。
我们
会想要一次构建两个项目,而不是到两个模块的目录下分别执行mvn命令
。Maven聚合(或者称为多模块)这一特性就是为该需求服务的。
为了能够使用一条命令就能构建account-email和account-persist两个模块,我们需要创建一个额外的名为account-aggregator的模块,然后通过该模块构建整个项目的所有模块。account-aggregator本身作为一个Maven项目,它必须要有自己的POM,不过,同时作为一个聚合项目,其POM又有特殊的地方。如下为account-aggregator的pom.xml内容
上述POM依旧使用了账户注册服务共同的groupId com.juvenxu.mvnbook.account,artifactId为独立的account-aggregator,版本也与其他两个模块一致,为1.0.0-SNAPSHOT。
account-aggregator的内容仅是一个pom.xml文件,它不像其他模块那样有src/main/java、src/test/java等目录
。这也是容易理解的,聚合模块仅仅是帮助聚合其他模块构建的工具,它本身并无实质的内容
。
这里的第一个特殊的地方为packaging,其值为POM。回顾account-email和account-persist,它们都没有声明packaging,即使用了默认值jar
。对于聚合模块来说,其打包方式packaging的值必须为pom,否则就无法构建
。
modules
,这是实现聚合的最核心的配置
。用户可以通过在一个打包方式为pom的Maven项目中声明任意数量的module元素来实现模块的聚合。
为了方便用户构建项目,通常将聚合模块放在项目目录的最顶层
,其他模块则作为聚合模块的子目录存在。
关于目录结构还需要注意的是,聚合模块与其他模块的目录结构并非一定要是父子关系
。图8-2展示了另一种平行的目录结构
。
如果使用平行目录结构,聚合模块的POM也需要做相应的修改,以指向正确的模块目录:
在面向对象世界中,程序员可以使用类继承在一定程度上
消除重复
,在Maven的世界中,也有类似的机制能让我们抽取出重复的配置,这就是POM的继承。一般用于抽取相同的依赖和插件,相同的groupId和version
,如果遇到子模块需要使用和父模块不一样的groupId或者version的情况,那么用户完全可以在子模块中显式声明
。
在account-aggregator下创建一个名为account-parent的子目录,然后在该子目录下建立一个所有除account-aggregator之外模块的父模块。为此,在该子目录创建一个pom.xml文件:
它使用了与其他模块一致的groupId和version,使用的artifactId为account-parent表示这是一个父模块。需要特别注意的是,
它的packaging为pom,这一点与聚合模块一样,作为父模块的POM,其打包类型也必须为pom
。
由于父模块只是为了帮助消除配置的重复,因此它本身不包含除POM之外的项目文件,也就不需要src/main/java/之类的文件夹了。
上述POM中使用parent元素声明父模块
,parent下的子元素groupId、artifactId和version指定了父模块的坐标,这三个元素是必须的。元素relativePath表示父模块POM的相对路径
,该例中的../account-parent/pom.xml表示父POM的位置在与account-email/目录平行的account-parent/目录下
。当项目构建时,Maven会首先根据relativePath检查父POM,如果找不到,再从本地仓库查找。relativePath的默认值是…/pom.xml,也就是说,Maven默认父POM在上一层目录下。
·
groupId
:项目组ID,项目坐标的核心元素。
·version
:项目版本,项目坐标的核心元素。
·description:项目的描述信息。
·organization:项目的组织信息。
·inceptionYear:项目的创始年份。
·url:项目的URL地址。
·developers:项目的开发者信息。
·contributors:项目的贡献者信息。
·distributionManagement
:项目的部署配置。
·issueManagement:项目的缺陷跟踪系统信息。
·ciManagement:项目的持续集成系统信息。
·scm:项目的版本控制系统信息。
·mailingLists:项目的邮件列表信息。
·properties:自定义的Maven属性。
·dependencies
:项目的依赖配置。
·dependencyManagement
:项目的依赖管理配置。
·repositories:项目的仓库配置。
·build:包括项目的源码目录配置、输出目录配置、插件配置、插件管理配置等。
·reporting:包括项目的报告输出目录配置、报告插件配置等。
场景:将现有子模块的相同依赖抽取到父模块,但是我们无法确定将来添加的子模块就一定需要这几个依赖。
Maven提供的
dependencyManagement
元素既能让子模块继承到父模块的依赖配置,又能保证子模块依赖使用的灵活性
。
在dependencyManagement元素下的依赖声明不会引入实际的依赖,不过它能够约束dependencies下的依赖使用
。
一句话,dependencyManagement元素下的依赖声明不引入实际依赖,只是声明依赖,此pom被继承后,也继承了dependencyManagement依赖声明,只需配置groupId和artifactId,省去了version(dependencyManagement已经声明了)
父POM
首先该父POM
使用了5.9.2节介绍的方法,将springframework和junit依赖的版本以Maven变量的形式提取了出来,不仅消除了一些重复,也使得各依赖的版本处于更加明显的位置。
这里使用dependencyManagement声明的依赖既不会给account-parent引入依赖,也不会给它的子模块引入依赖,不过这段配置是会被继承的
。
子POM
上述POM中的依赖配置较原来简单了一些,所有的springframework依赖只配置了groupId和artifactId,省去了version
,而junit依赖不仅省去了version,还省去了依赖范围scope。这些信息可以省略是因为account-email继承了account-parent中的dependencyManagement配置
,完整的依赖声明已经包含在父POM中,子模块只需要配置简单的groupId和artifactId就能获得对应的依赖信息,从而引入正确的依赖。
例如想要在另外一个模块中使用与代码清单8-14完全一样的dependencyManagement配置,除了复制配置或者继承这两种方式之外,还可以使用import范围依赖将这一配置导入
注意,上述代码中依赖的type值为pom,import范围依赖由于其特殊性,一般都是指向打包类型为pom的模块。如果有多个项目,它们使用的依赖版本都是一致的,则就可以定义一个使用dependencyManagement专门管理依赖的POM,然后在各个项目中导入这些依赖管理配置。
Maven提供了dependencyManagement元素帮助管理依赖,
类似地,Maven也提供了pluginManagement元素帮助管理插件
。
在该元素中配置的依赖不会造成实际的插件调用行为,当POM中配置了真正的plugin元素,并且其groupId和artifactId与pluginManagement中配置的插件匹配时,pluginManagement的配置才会影响实际的插件行为。
子模块声明使用了maven-source-plugin插件,同时又继承了父模块的pluginManagement配置。如果子模块不需要使用父模块中pluginManagement配置的插件,可以尽管将其忽略。如果子模块需要不同的插件配置,则可以自行配置以覆盖父模块的pluginManagement配置。
当项目中的多个模块有同样的插件配置时,应当将配置移到父POM的pluginManagement元素中。即使各个模块对于同一插件的具体配置不尽相同,也应当使用父POM的pluginManagement元素统一声明插件的版本。甚至可以要求将所有用到的插件的版本在父POM的pluginManagement元素中声明,子模块使用插件时不配置版本信息,这么做可以统一项目的插件版本,避免潜在的插件不一致或者不稳定问题,也更易于维护。
共同点:聚合POM与继承关系中的父POM的packaging都必须是pom,
同时,聚合模块与继承关系中的父模块除了POM之外都没有实际的内容,如图8-3所示。
在现有的实际项目中,通常会发现一个POM既是聚合POM,又是父POM
合并聚合和继承功能后的account-parent
该POM的打包方式为pom,它包含了一个modules元素,表示用来聚合account-persist和account-email两个模块,它还包含了properties、dependencyManagement和pluginManagement元素供子模块继承。
相应地,account-email和account-persist的POM配置也要做微小的修改。本来account-parent和它们位于同级目录,因此需要使用值为../account-parent/pom.xml的relativePath元素。现在新的account-parent在上一层目录,这是Maven默认能识别的父模块位置,因此不再需要配置relativePath
多模块的Maven项目中,
反应堆(Reactor
)是指所有模块组成的一个构建结构
。对于单模块的项目,反应堆就是该模块本身,但对于多模块项目来说,反应堆就包含了各模块之间继承与依赖的关系,从而能够自动计算出合理的模块构建顺序。
反应堆的构建顺序
几个模块的构建次序显然与它们在聚合模块中的声明顺序不一致,account-parent跑到了account-email前面,这是为什么呢?
实际的构建顺序是这样形成的:
Maven按序读取POM,如果该POM没有依赖模块,那么就构建该模块,否则就先构建其依赖模块,如果该依赖还依赖于其他模块,则进一步先构建依赖的依赖
。该例中,account-aggregator没有依赖模块,因此先构建它,接着到account-email,它依赖于account-parent模块,必须先构建account-parent,然后再构建account-email,最后到account-persist的时候,由于其依赖模块已经被构建,因此直接构建它。
当出现模块A依赖于B,而B又依赖于A的情况时,Maven就会报错
。
通过建立自己的私服,就可以降低中央仓库负荷、节省外网带宽、加速Maven构建、自己部署构件等,从而高效地使用Maven。
有三种专门的Maven仓库管理软件可以用来帮助大家建立私服:Apache基金会的Archiva、JFrog的Artifactory和Sonatype的Nexus。
Maven的重要职责之一就是自动运行单元测试,它通过
maven-surefire-plugin
与主流的单元测试框架JUnit 3、JUnit 4以及TestNG集成,并且能够自动生成丰富的结果报告。
包含与排除测试用例
自动运行以Tests结尾的测试类
排除运行测试类
作为最核心的敏捷实践之一——
持续集成(Continuous Integration)
越来越受到广大开发人员的喜爱和推崇。Hudson
则是最流行的开源持续集成服务器软件
。
简单地说,
持续集成
就是快速且高频率地自动构建项目的所有源码,并为项目成员提供丰富的反馈信息
。
·快速:集成的速度要尽可能地快,开发人员不希望自己的代码提交半天之后才得到反馈。
·高频率:频率越高越好,例如每隔一小时就是个不错的选择,这样问题才能尽早地被反映出来。
·自动:持续集成应该是自动触发并执行的,不应该有手工参与。
·构建:包括编译、测试、审查、打包、部署等工作。
·所有源码:所有团队成员提交到代码库里的最新的源代码。
·反馈:持续集成应该通过各种快捷的方式告诉团队成员最新的集成状态,当集成失败的时候,反馈报告应该尽可能地反映失败的具体细节。
一个典型的持续集成场景
是这样的:
开发人员对代码做了一些修改,在本地运行构建并确认无误之后,将更改提交到代码库。具有高配置硬件的持续集成服务器
每隔30分钟查询代码库一次,发现更新之后,签出所有最新的源代码,然后调用自动化构建工具(如Maven)构建项目,该过程包括编译、测试、审查、打包和部署等
。然而不幸的是,另外一名开发人员在这一时间段也提交了代码更改,两处更改导致了某些测试的失败,持续集成服务器基于这些失败的测试创建一个报告,并自动发送给相关开发人员。开发人员收到报告后,立即着手调查原因,并尽快修复。
一次完整的集成往往会包括以下6个步骤:
1)
持续编译
:所有正式的源代码都应该提交到源码控制系统中(如Subversion),持续集成服务器按一定频率检查源码控制系统,如果有新的代码,就触发一次集成,旧的已编译的字节码应当全部清除,然后服务器编译所有最新的源码。
2)持续数据库集成
:在很多项目中,源代码不仅仅指Java代码,还包括了数据库SQL脚本,如果单独管理它们,很容易造成与项目其他代码的不一致,并造成混乱。持续集成也应该包括数据库的集成,每次发现新的SQL脚本,就应该清理集成环境的数据库,重新创建表结构,并填入预备的数据。这样就能随时发现脚本的错误,此外,基于这些脚本的测试还能进一步发现其他相关的问题。
3)持续测试
:有了JUnit之类的框架,自动化测试就成了可能。编写优良的单元测试并不容易,好的单元测试必须是自动化的、可重复执行的、不依赖于环境的,并且能够自我检查的。除了单元测试,有些项目还会包含一些依赖外部环境的集成测试。所有这些测试都应该在每次集成的时候运行,并且在发生问题的时候能产生具体报告。
4)持续审查
:诸如Checkstyle和PMD之类的工具能够帮我们发现代码中的坏味道(Bad Smell),持续集成可以使用这些工具来生成各类报告,如测试覆盖率报告、Checkstyle报告、PMD报告等。这些报告的生成频率可以低一些,如每日生成一次,当审查发现问题的时候,可以给开发人员反馈警告信息。
5)持续部署
:有些错误只有在部署后才能被发现,它们往往是具体容器或者环境相关的,自动化部署能够帮助我们尽快发现这类问题。
6)持续反馈
:持续集成的最后一步的反馈,通常是一封电子邮件。在重要的时候将正确的信息发送给正确的人。如果开发者一直受到与自己无关的持续集成报告,他慢慢地就会忽略这些报告。基本的规则是:将集成失败报告发送给这次集成相关的代码提交者,项目经理应该收到所有失败报告。
持续集成有着很多好处:
·
尽早暴露问题
:越早地暴露问题,修复问题代码的成本就越低。持续集成高频率地编译、测试、审查、部署项目代码,能够快速地发现问题并及时反馈。
·减少重复操作
:持续集成是完全自动化的,这就避免了大量重复的手工劳动,开发人员不再需要手动地去签出源码,一步步地编译、测试、审查、部署。
·简化项目发布
:每日高频率的集成保证了项目随时都是可以部署运行的,如果没有持续集成,项目发布之前将不得不手动地集成,然后花大量精力修复集成问题。
·建立团队信心
:一个优良的持续集成环境能让团队随时对项目的状态保持信心,因为项目的大部分问题区域已经由持续集成环境覆盖了。
本章内容
·Web项目的目录结构
·account-service
·account-web
·使用jetty-maven-plugin进行测试
·使用Cargo实现自动化部署
·小结
前面讨论的只有打包类型为JAR或者POM的Maven项目。但在现今的互联网时代,我们创建的大部分应用程序都是Web应用,**在Java的世界中,Web项目的标准打包方式是WAR。**因此本章介绍一个WAR模块——account-web,它也来自于本书的账户注册服务背景案例。在介绍该模块之前,本章还会先实现account-service。此外,还介绍如何借助jetty-maven-plugin来快速开发和测试Web模块,以及使用Cargo实现Web项目的自动化部署。
基于Java的Web应用,其标准的打包方式是WAR。WAR与JAR类似,只不过它可以包含更多的内容,如JSP文件、Servlet、Java类、web.xml配置文件、依赖JAR包、静态web资源(如HTML、CSS、JavaScript文件)等。
一个WAR包下至少包含两个子目录:
META-INF
和WEB-INF
。
前者包含了一些打包元数据信息
,我们一般不去关心;
后者是WAR包的核心
,WEB-INF下必须包含一个Web资源表述文件web.xml
,它的子目录classes包含所有该Web项目的类,而另一个子目录lib则包含所有该Web项目的依赖JAR包,classes和lib目录都会在运行的时候被加入到Classpath中
。
WAR包中有一个lib目录包含所有依赖JAR包,但Maven项目结构中没有这样一个目录,这是因为依赖都配置在POM中,Maven在用WAR方式打包的时候会根据POM的配置从本地仓库复制相应的JAR文件。
显式指定Web项目的打包方式为war
Web项目
比较特殊的地方在于:它还有一个Web资源目录,其默认位置是src/main/webapp
/。
一个典型的Web项目
的Maven目录结构如下:
可以看到,img,css,js,html,jsp在web项目
里是在webapp目录下,而在war文件中是在根目录下
Cargo是一组帮助用户操作Web容器的工具,它能够帮助用户实现自动化部署,而且它几乎支持所有的Web容器,如Tomcat、JBoss、Jetty和Glassfish等。Cargo通过cargo-maven2-plugin提供了Maven集成,Maven用户可以使用该插件将Web项目部署到Web容器中。
本节以Tomcat 为例,介绍如何自动化地将Web应用部署至本地或远程Web容器中。
Cargo支持
两种本地部署的方式
,分别为standalone模式
和existing模式
。
在standalone模式
中,Cargo会从Web容器的安装目录复制一份配置到用户指定的目录
,然后在此基础上部署应用,每次重新构建的时候,这个目录都会被清空,所有配置被重新生成。
而在existing模式
中,用户需要指定现有的Web容器配置目录
,然后Cargo会直接使用这些配置并将应用部署到其对应的位置
。
cargo-maven2-plugin的groupId是org.codehaus.cargo,这不属于官方的两个Maven插件groupId,因此用户需要将其添加到settings.xml的pluginGroup元素中以方便命令行调用。
上述cargo-maven2-plugin的具体配置包括了container和configuration两个元素,configuration的子元素type表示部署的模式(这里是standalone)。与之对应的,configuration的home子元素表示复制容器配置到什么位置,这里的值为${project.build.directory}/tomcat6x,表示构建输出目录,即target/下的tomcat6x子目录。container元素下的containerId表示容器的类型,home元素表示容器的安装目录。基于该配置,Cargo会从D:\cmd\apache-tomcat-6.0.29目录下复制配置到当前项目的target/tomcat6x/目录下。
默认情况下,Cargo会让Web容器监听8080端口。可以通过修改Cargo的cargo.servlet.port属性来改变这一配置
现在,要让Cargo启动Tomcat并部署应用,只需要运行:
mvn cargo:start
地址为http:localhost:8081/account-web-1.0.0-SNAPSHOT/signup.jsp。
部署应用至远程Web容器
对于远程部署的方式来说,container元素的type子元素的值必须为remote。如果不显式指定,Cargo会使用默认值installed,并寻找对应的容器安装目录或者安装包,对于远程部署方式来说,安装目录或者安装包是不需要的。上述代码中configuration的type子元素值为runtime,表示既不使用独立的容器配置,也不使用本地现有的容器配置,而是依赖于一个已运行的容器。properties元素用来声明一些容器热部署相关的配置。例如,这里的Tomcat 6就需要提供用户名、密码以及管理地址。需要注意的是,这部分配置元素对于所有容器来说不是一致的,读者需要查阅对应的Cargo文档。
有了上述配置后,就可以让Cargo部署应用了。运行命令如下:
mvn cargo:redeploy
如果容器中已经部署了当前应用,Cargo会先将其卸载,然后再重新部署。
需要分清
版本管理(Version Management)
和版本控制(Version Control)
的区别。版本管理是指项目整体版本的演变过程管理,如从1.0-SNAPSHOT到1.0,再到1.1-SNAPSHOT。版本控制是指借助版本控制工具(如Subversion)追踪代码的每一个变更。本章重点讲述的是版本管理
1.3.4-beta-2
表示了该项目或产品的第一个重大版本的第三个次要版本的第四次增量版本的beta-2里程碑。
Maven的版本号定义约定是这样的:
<主版本>.<次版本>.<增量版本>-<里程碑版本>
一般来说,主版本和次版本都会声明,但增量版本和里程碑就不一定有。
里程碑的含义:可能有新的特性或功能,但表示不是非常稳定,还需要很多测试。
水岸齐天
Maven