使用Maven创建应用
介绍
将要创建的应用名叫Proficio,拉丁语的"help"。
设置应用程序的目录结构
在设置Proficio的目录结构时,注意Maven强调的实践标准化和构建模块化构建是很重要的。
这种实践自然将产生分离的可重用的开发工程。决定如何最优化的分解应用的原则叫做“分离关注点(Separation of Concerns)”原则,即SoC原则。
SoC有助于识别、封装、操作于有相关特殊概念、目标、任务或目的的软件片段。关注点是组织和分解软件的动力,更多的易于管理和理解的部分,每个都用于说明一个或多个特定关注点。
如上所述,Proficio样例工程将被设置为多个Maven模块:
- Proficio API:Proficio的应用编程接口,它包含了一套接口。这些接口是主要组件(例如store)的API。
- Proficio CLI:提供Proficio的命令行接口。
- Proficio Core:API的实现。
- Proficio Model:Proficio应用的数据模型,它包含了将被整个Proficio工程所使用的所有的类。
- Proficio Stores:这个模块处理包含所有的存储模块。Proficio有一个简单的memory-based和XStream-based存储。
在Proficio的顶层POM中,可以看到所有子模块元素。一个模块指向另一个Maven工程,实际上它是指向另一个POM。Proficio的顶层POM文件如下:
4.0.0 com.devzuz.mvnbook.proficio proficio pom 1.0-SNAPSHOT Maven Proficio http://maven.apache.org [...][...] proficio-model proficio-api proficio-core proficio-stores proficio-cli
上面的版本号1.0-SNAPSHOT。对于一个有多模块的应用,通常将所有模块一起发布,所有模块使用一个公共的版本号。这是推荐的一种方式。
注意上面的packaging元素,这里它被设置为pom。对于包含模块的POM文件,packaging必须设置为pom:这告诉Maven你准备创建一个模块集。
Proficio应用的模块打包类型:
proficio-api | jar |
proficio-cli | jar |
proficio-core | jar |
proficio-module | jar |
proficio-stores | pom |
proficio-stores模块有两个子模块。
使用工程继承
Maven最重要的功能之一就是工程继承。使用工程继承允许你在一个地方规定组织机构信息,规定部署信息,或规定通用的依赖。由Proficio工程产生的每个工程的POM文件,每个的顶部都有:
[...][...] com.devzuz.mvnbook.proficio proficio 1.0-SNAPSHOT
这个片段允许你从指定的顶层的POM继承。顶层POM中指定了依赖JUnit 3.8.1。在这种情况下子工程中不再申明这个依赖也可以使用这个包。
为了了解在继承处理时发生的东西可以执行mvn help:effective-pom命令。这个命令将显示出最终的POM。在proficio的子工程中执行这个命令时可以看到依赖中出现了JUnit 3.8.1。
管理依赖关系
Maven可以让不同的工程共享程序包。
可以在顶层的POM中描述所有子工程共享的依赖。
例如Proficio的顶层POM:
[...] [...] com.devzuz.mvnbook.proficio proficio-model ${project.version} com.devzuz.mvnbook.proficio proficio-api ${project.version} com.devzuz.mvnbook.proficio proficio-store-memory ${project.version} com.devzuz.mvnbook.proficio proficio-store-xstream ${project.version} com.devzuz.mvnbook.proficio proficio-core ${project.version} org.codehaus.plexus plexus-container-default 1.0-alpha-9
注意${project.version}指定了版本,它与应用的版本对应。
在dependencyManagement一节,有多个Proficio依赖并且还依赖于Plexus IoC container。dependencyManagment元素与顶层POM的dependencies有重要区别。
dependencyManagement元素中包括的dependencies元素仅用于说明引用的版本号,对并不影响工程的依赖关系图,然而顶层的dependencies元素将影响依赖关系图。查看proficio-api模块的POM将只看到引用而没有指定版本:
[...] com.devzuz.mvnbook.proficio proficio-model
这个依赖的版本号是从Proficio的顶层POM文件的dependencyManagement继承过来的。 dependencyManagement指定了引用proficio-model的版本号为1.0-SNAPSHOT(被设置 为${project.version})这个版本号将注入到上面的依赖中。dependencyManagement中说明的dependencies 只用于当某个依赖没有版本号的情况。
使用快照
当开发的应用具有多个模块时,通常每个模块的版本都在变更。API可能正在经历变迁或你的实现正在发生改变,或者在进 行重构。你在构建时需要非常容易的实时获取最新版本,这是Maven的快照(snapshot)的概念。快照是Maven的一个artifact。查看 Proficio的顶层POM可以看到指定了快照版本。
[...] 1.0-SNAPSHOT [...] com.devzuz.mvnbook.proficio proficio-model ${project.version} com.devzuz.mvnbook.proficio proficio-api ${project.version} org.codehaus.plexus plexus-container-default 1.0-alpha-9
指定快照版本作为依赖时Maven将会查找新版本而不像手工指定版本时那样操作。快照依赖假定总是在变更,因此 Maven将尝试更新它们。默认情况下Maven将以天为单位查找新版本,但你可以使用命令行参数-U来强制它查找新版本。当指定非快照的依赖时 Maven将下载依赖并且不会进行重试。
解决依赖冲突和使用版本号范围
在Maven 2.0中通过引入依赖传递,使得可以在简单的POM文件中只指定你直接需要的依赖,并且Maven可以计算出完整的依赖关系图。但是,随着图的增涨,不可 避免的将产生一个或多个artifacts需要依赖的不同版本。这种情况下,Maven必须选择使用哪个版本。
Maven通常选择这个关系树中顶层的“最接近的版本(nearest)”,Maven选择版本号跨度最小的版本。如果在POM中指定了版本,则将被使用而不管其它的原因。但这种方式也有下面的限制:
- 选择的版本可能没有某个依赖的组件所需要的功能。
- 如果在相同的级别先择了多个不同的版本,则结果将是不明确的。
手工解决冲突,可以从依赖树中移除不正确的版本,或者可以用正确的版本来覆盖掉树中的版本。移除不正确的版本需要在运行Maven时指定-X标识来找出不正确的版本。例如,如果在proficio-core模块运行mvn -X test将输出:
proficio-core:1.0-SNAPSHOT junit:3.8.1 (selected for test) plexus-container-default:1.0-alpha-9 (selected for compile) plexus-utils:1.0.4 (selected for compile) classworlds:1.1-alpha-2 (selected for compile) junit:3.8.1 (not setting scope to compile; local scope test wins) proficio-api:1.0-SNAPSHOT (selected for compile) proficio-model:1.0-SNAPSHOT (selected for compile) plexus-utils:1.1 (selected for compile)
这样可以找出当前操作所使用的详细版本信息,一旦找出了不正确的版本,你可以将它从依赖关系图中移除。例如,这个例子中,plexus-utils出现了 两次,Proficio需要1.1版。为确保这个依赖,可以修改plexus-container-default的依赖,例如:
org.codehaus.plexus plexus-container-default 1.0-alpha-9 org.codehaus.plexus plexus-utils
这保证了Maven将忽略1.04版的plexus-utils,而使用1.1版。
另一种方法确保在依赖中使用特定版本,是将它直接包含在POM中,例:
org.codehaus.plexus plexus-utils 1.1 runtime
但是这种方式是不被推荐的除非你是在制作一个绑定了自己的依赖的artifact,并且它自身不会作为一个依赖(例如,是一个WAR文件)。原因是这种做法歪曲了真实的依赖关系图,在工程自身作为依赖被重用时将导致问题。
在这里指定了runtime作用范围。这是因为,在这种情况下,依赖只是用于打包而不是编译。实际上,如果依赖是在编译时需要,它应该总是出现在当前POM的依赖中——而不管另一个依赖是否使用了它。
上面的这些解决都只是理想化的,但它可以提高你自己的依赖的质量,避免你在构建自己的产品时的风险。这一点在构建一个应用程序框架时是非常重要的,因为它将广泛的被其它人使用。为达到这个目标,可以使用版本范围来替代这种方式。
当上面的plexus-utils的版本被设置为1.1时,标明首选依赖的是1.1版,但其它版本可能也能够接受。Maven并不知道哪个版本可以 工作,因此当与其它依赖冲突时,Maven确保所有的版本使用前面描述的“最近依赖(nearest dependency)”技术来决定使用哪个版本。
但是,你可能需要一个plexus-utils 1.1版中的功能。这时,依赖应该指定为下面的形式:
org.codehaus.plexus plexus-utils [1.1,)
这表示在版本冲突时仍将使用nearest dependency技术,但是版本号必须符合给定的范围。如果版本不匹配,则下一个最接近的版本将被测试,如此继续。最后,如果没有匹配的版本,或本来 就没有冲突,则使用指定的版本[1.1,)。这表示将从仓库中获取最小的版本号大于或等于1.1的版本。
版本范围范例表:
(,1.0] | 小于或等于1.0 |
[1.2,1.3] | 处于1.2和1.3之间(含1.3) |
[1.0,2.0) | 大于或等于1.0,但小于2.0 |
[1.5,) | 大于或等于1.5 |
(,1.1),(1.1,) | 除1.1外的任何版本 |
通过指定使用的版本范围,使得构建时依赖管理机制更加可靠并且减少异常的情况。但应该避免过度的详细。例如,如果两个版本范围依赖图不交叉,那么构建将失败。
为了解版本范围是如何工作的,需要了解版本是怎样进行比较的。下面展示了Maven是如何分割版本号的。
1.0.1-20060211.131141-1
从左至右依次为:
1为主版本号
0为次版本号
1为Bug修正号
20060211.131141为限定版本号
1为构建号
在目前的版本方案中,快照版本是一种特殊的情况,在这种情况下限定号和构建号可以同时存在。在正式版本中,可以只提供限定号或只提供构建号。有意设 置的限定号标识出了一个较优先的版本(例如:alpha-1,beta-1,rc1)。对于快照版本,限定号必须是文本“snapshot”或时间戳。构 建号是一个自增号在发布时标明是补丁构建。
版本中的元素依次决定哪个版本较新——首先是主版本号,如果主版本号相等,则比较次版本号,接下来是Bug修正号,限定号,最后比较构建号。带限定 号的版本比不带限定号的版本要旧;比如1.2-beta比1.2旧。包含了构建号的版本比不带构建号的版本新;比如1.2-beta-1比1.2- beta新。某些情况下,版本可能会不匹配这个语法。在这些情况下,两个版本号将作为字符串进行比较。
当使用快照版本测试编译发布版本或自己测试发布测试版本时应该将它们部署到快照仓库,这将在第七章讨论。这保证了在版本范围中的beta版本才会使用,除非工程显式的申明了使用快照版本。
最后要注意的是当使用版本范围时版本更新是如何被决定的。这个机制与前面介绍的快照版本更新机制是相同的,即每天从版本库中更新一次版本。但是,这可以通过配置每个仓库来设置更新的频率,或在命令行使用-U参数强制Maven执行更新。
例:
[...] interval:60
利用构建生命周期
第二章中将Maven描述为一个正确调整插件执行方式或顺序的应用框架,这实际上就是Maven的默认构建生命周期。 Maven默认的构建生命周期对于大多数工程来说不需要增加任何内容就可以满足了——当然,有时工程需要增加不同的内容到Maven的默认构生命周期来满 足构建的需求。
例如,Proficio需要从model生成Java源码。Maven通过允许申明插件来满足这个需求,将它绑定到Maven默认生命周期的一个标准阶段——generate-sources阶段。
Maven的插件是为特定任务而创建的,这意味着插件将被绑定到默认的生命周期的一个特定阶段。在Proficio中,Modello插件被用于生 成Proficio的数据模型的Java源码。查看proficio-model的POM可以看到plugins元素配置了Modello插件。
com.devzuz.mvnbook.proficio proficio 1.0-SNAPSHOT 4.0.0 proficio-model jar Proficio Model org.codehaus.modello modello-maven-plugin 1.0-alpha-5 java 1.0.0 false src/main/mdo/proficio.mdo
这与第二章中maven-compiler-plugin的申明非常相似,但这里可以看到额外的execution元素。Maven中的插件可以有多个goal,因此你需要指定你希望运行插件的哪个goal,这可以通过在execution中的goal元素来指定。
使用Profiles
Profile是Maven提供的用于创建构建生命周期中的不同环境变量、不同的平台、不同JVM、不同的测试数据 库、或引用不同的本地文件系统的方法。通常你可以在POM中封装,以保证构建的可移植性,但有时你需要考虑变化的交叉系统的情况,这也是Maven中引入 profile的原因。
Profile使用POM中的一个子集元素来指定,可以用多种方式来启用。Profile在构建时修改POM,意味着它将用于给不同的目标环境中的参数集(比如,在开发环境、测试环境、产品环境下应用服务器根路径)不同参数值。
Profile也可以很容易的实现团队中不同成员生成不同的构建结果。也可以通过profile阻止构建的移植性。Profile可以定义在下面三个地方:
- Maven的配置文件(通常是
/.m2/settings.xml) - 与POM同一目录下的名为profiles.xml的文件
- POM文件中
优先级依次为POM文件、profiles.xml、settings.xml。这也是Maven中的基本原则。
settings.xml中设置profile会影响所有的构建,因此它适合“全局”的profiles。profiles.xml允许设置单个工 程的构建而不用修改POM。基于POM的profiles是首选方式,因为这样更具有移植性(它们将在布署时发布到仓库,对于源于仓库的子构建或依赖来说 也同样有效)。
因为移植性的原因,那些不会发布到仓库中的文件不允许修改任何基础构建。因此,profiles.xml和settings.xml只允许定义:
- 仓库repositories
- 插件仓库pluginRepositories
- 属性properties
其它的信息必须在POM的profile中指定或在POM自身指定。例如,如果settings.xml中有一个profile它可以注入一个依 赖,你的工程运行需要settings注入的依赖,一旦这个工程部部署到仓库中它将不能解决它的依赖。因为其中一个依赖设置在settings.xml的 profile中了。
注意:respositories、pluginRepositories和properties也可以在POM内部的profiles指定。因此,在POM外部指定的profiles只允许使用POM内部指定的profiles选项的一个小的子集。
可以在POM中定义的profile:
- repositories
- pluginRepositories
- dependencies
- plugins
- properties(not actually available in the main POM, but used behind the scenes)
- modules
- reporting
- dependencyManagement
- distributionManagement
构建元素的子集,由下面组成:
- defaultGoal
- resources
- testResources
- finalName
有多种方法启用profiles:
- 在命令行上使用-P选项。这个选项接收以逗号名分隔的profile的id列表。当指定这个选项时,这些指定在参数中的profiles将被激活。例:
mvn -Pprofile1,profile2 install
- Profiles可以在Maven settings文件通过activeProfiles段中激活。这段接收activeProfile元素的列表,每个都包括了一个profile-id。注意你必须在settings.xml文件中定义了这些profiles。例:
[...] profile1 [...][...] profile1
- Profiles可以在检测到构建环境时自动触发。这些在profile的activation段设置。目前这种检测仅限于匹配JDK版本号的前缀、当前系统属性或系统属性的值。例如:
profile1 [...]1.4
这个激器将在JDK的版本以1.4开始时被触发。
profile1 [...]debug
上面的profile将在系统属性debug被指定时被触发。
profile1 [...]environment test
最后的这个例子将在系统属性environment属性设置为true时激活。
在熟悉profiles后,可以使用它组装不同的Proficio系统:一个配置方案是memory-base存储,另一个是XStream-based存储。这些将在proficio-cli模块中使用。proficio-cli模块的profile定义如下:
[...] memory maven-assembly-plugin src/main/assembly/assembly-store-memory.xml memory xstream maven-assembly-plugin src/main/assembly/assembly-store-xstream.xml xstream
可以看到两个profiles:一个id为memory另一个id为xstream。在每个profile中你可以配置插件。也可以看到profile通 过一个系统属性进行激活。这个例子依赖于前面已经执行过的一些构建步骤,因此应该先在工程的顶级目录执行mvn install确保需要的组件被安装到本地仓库。
如果想基于memory-based存储进行构建,可以执行:
mvn -Dmemory clean assembly:assembly
如果想基于XStream-based存储进行,可以执行:
mvn -Dxstream clean assembly:assembly
这两种方式构建的结果都保存在target目录中,如果对输出使用jar tvf命令,可以看到memory-base方式构建时只包含了proficio-store-memory-1.0-SNAPSHOT.jar,当使用 XStream-based方式时,只包含了proficio-store-xstream-1.0-SNAPSHOT.jar。
部署应用
当前Maven支持多种部署方式包括文件系统部署、SSH2部署、SFTP部署、FTP部署和外部SSH部署。为了进行部署,需要正确的配置POM中的distributionManagement元素,通常是在顶级POM中,因为子POM可以继承这些信息。
文件系统部署
[...] [...] proficio-repository Proficio Repository file://${basedir}/target/deploy
SSH2部署
[...] [...] proficio-repository Proficio Repository scp://sshserver.yourcompany.com/deploy
SFTP部署
[...] [...] proficio-repository Proficio Repository sftp://ftpserver.yourcompany.com/deploy
外部SSH部署
前面三个部署方式是包含在Maven内部的,因此只需要distributionMangement元素,但使用外部SSH命令部署则还要使用一个构建扩展。
[...] proficio-repository Proficio Repository scpexe://sshserver.yourcompany.com/deploy [...] org.apache.maven.wagon wagon-ssh-external 1.0-alpha-6
这个构建扩展指定使用Wagon外部SSH提供都,它将你的文件移动到远程服务器上。Wagon是Maven中通用的用于传送的机制。
FTP部署
FTP部署也必须指定一个构建扩展。
[...] proficio-repository Proficio Repository ftp://ftpserver.yourcompany.com/deploy [...] org.apache.maven.wagon wagon-ftp 1.0-alpha-6
一旦配置完POM后,可以执行
mvn deploy
进行部署。
为应用程序创建Web站点
前面已经完成了Proficio的构建、测试、部署,现在可以为这个应用创建一个标准的Web站点。对于Procio这样的应,推荐在顶级目录创建用于生成站点的资源目录。
所有用于生成站点的文件保存在src/site目录。src/site目录中也有子目录保存支持文档。Maven支持大量同的文件格式。
当前支持得最好的格式:
- XDOC格式,它是一个被Apache广泛使用的简单的XML格式。
- APT(Almost Plain Text),与wiki格式类似的格式。
- FML格式,FAQ格式。一个简单的XML格式管理FAQ。
- DocBook Simple格式,它是一个比完整的DocBook格式简单一些的格式。
Maven也有限的支持:
- Twiki格式,这是一种流行的Wiki格式。
- Confluence格式,这是另一种流行的Wiki格式。
- DocBook格式。
在后面的章节将了解支持较好的那些格式,但你应该熟悉下面的目标:
- 配置banner的外观。
- 配置站点的皮肤。
- 配置发布数据的格式。
- 配置banner下显示的链接。
- 配置放入生成的页面的 元素中的信息。
- 配置显示在导航栏中的菜单项。
- 配置项目报表的外观。
查看Proficio应用的src/site可以看到站点的描述:
Proficio http://maven.apache.org/ Proficio http://maven.apache.org/images/apache-maven project.png org.apache.maven.skins maven-default-skin 1.0-SNAPSHOT ${reports}
这是一个相当标准的Web站点描述,每个元素的说明如下:
bannerLeft and bannerRight | 这些元素包括名称、href和可选的src元素,可以用于图像。 |
skin | 这个元素看起来像是依赖的描述(使用了相同的机制来获取皮肤)控制站点使用的皮肤。 |
publishDate | 发布日期的格式,使用的Java类中的SimpleDateFormat。 |
body/links | 控制banner下的链接引用只需要name和href。 |
body/head | head元素允许你插入任何信息到生成的页面。可以加metadata、script(如Google Analytics)。 |
Maven中最流行的功能之一就是花较少的功夫就可以生成标准的报表。只要简单的在站点描述中包含${reports}引用,默认情况下是包含的,工程信息报表将自动生成的被添加上来。标准的工程信息报表包含下面的内容:
- 依赖关系报告
- 邮件列表报告
- 持续集成报告
- 源码仓库报告
- 发行版本跟踪报告
- 工程团队报告
- 版权
尽管标准报表很有用,通常你需要自定义工程的报表。报表的创建和显示控制是在POM的build/reports元素中。你可以选择生成报表的信息,只要列举出需要包含在站点中的报表即可。这个插件的配置方式如下:
[...] [...] [...][...] org.apache.maven.plugins maven-project-info-reports-plugin dependencies project-team mailing-list cim license scm
执行
mvn site
生成站点。
生成的站点放在target目录下。如果有其它的资源,如图片、PDF等可以存放在src/site/resources。当站点被生成时src/site/resources将被复制到站点的顶级目录。