关闭

Maven简单教程

标签: maven单元测试dependenciesjarservletjunit
6228人阅读 评论(2) 收藏 举报
分类:

                   Chapter 1. 介绍 Apache Maven

                   Chapter 1. 介绍 Apache Maven

1.1. Maven... 它是什么?

1.2. 约定优于配置(Convention Over Configuration)

1.3. 一个一般的接口

1.4. 基于Maven插件的全局性重用

1.5. 一个“项目”的概念模型

1.6. Maven是Ant的另一种选择么?

1.7. 比较Maven和Ant

1.8. 总结

虽然网络上有许多Maven的参考文章,但是没有一篇单独的,编写规范的介绍Maven的文字,它需要是一本细心编排的入门指南和参考手册。我们做的,正是试图提供这样的,包含许多使用参考的文字。

                   1.1. Maven... 它是什么?

如何回答这个问题要看你怎么看这个问题。 绝大部分Maven用户都称Maven是一个"构建工具":一个用来把源代码构建成可发布的构件的工具。构建工程师和项目经理会说Maven是一个更复杂的东西:一个项目管理工具。那么区别是什么? 像Ant这样的构建工具仅仅是关注预处理,编译,打包,测试和分发。像 Maven 这样的一个项目管理工具提供了构建工具所提供功能的超集。除了提供构建的功能,Maven还可以生成报告,生成Web站点,并且帮助推动工作团 队成员间的交流。

一个更正式的 Apache Maven 的定义: Maven是一个项目管理工具,它包含了一个项目对象模型 (Project Object Model),一组标准集合,一个项目生命周期(Project Lifecycle),一个依赖管理系统(Dependency Management System),和用来运行定义在生命周期阶段(phase)中插件(plugin)目标(goal)的逻辑。 当你使用Maven的时候,你用一个明确定义的项目对象模型来描述你的项目,然后 Maven 可以应用横切的逻辑,这些逻辑来自一组共享的(或者自定义的)插件。

别让Maven是一个"项目管理"工具的事实吓跑你。如果你只是在找一个构建工具,Maven能做这个工作。事实上,本书的一些章节将会涉及使用Maven来构建和分发你的项目。

                   1.2. 约定优于配置(Convention Over Configuration

约定优于配置是一个简单的概念。 系统,类库,框架应该假定合理的默认值,而非要求提供不必要的配置。流行的框架如 Ruby on Rails EJB3 已经开始坚持这些原则,以对像原始的 EJB 2.1 规范那样的框架的配置复杂度做出反应。一个约定优于配置的例子就像 EJB3 持久化,将一个 特殊的Bean持久化,你所需要做的只是将这个类标注为 @Entity 。 框架将会假定表名和列名是基于类名和属性名。系统也提供了一些钩子,当有需要的时候你可以重写这些名字,但是,在大部分情况下,你会发现使用框架提供的默认值会让你的项目运行的更快。

Maven通过给项目提供明智的默认行为来融合这个概念。 在没有自定义的情况下,源代码假定是在 ${basedir}/src/main/java,资源文件假定是在 ${basedir}/src/main/resources 。测试代码假定是在 ${basedir}/src/test 。项目假定会产生一个 JAR 文件。Maven 假定你想要把编译好的字节码放到 ${basedir}/target/classes 并且在 ${basedir}/target 创建一个可分发的 JAR 文件。虽然这看起来无关紧要,但是想想大部分基于 Ant 的构建必须为每个子项目定义这些目录。 Maven 对约定优于配置的应用不仅仅是简单的目录位置,Maven 的核心插件使用了一组通用的约定,以用来编译源代码,打包可分发的构件,生成 web 站点,还有许多其他的过程。 Maven 的力量来自它的"武断",它有一个定义好的生命周期和一组知道如何构建和装配软件的通用插件。如果你遵循这些约定,Maven 只需要几乎为零的工作——仅仅是将你的源代码放到正确的目录,Maven 将会帮你处理剩下的事情。

使用“遵循约定优于配置”系统的一个副作用是用户可能会觉得他们被强迫使用一种特殊的方法。当然 Maven 有一些核心观点不应该被怀疑,但是其实很多默认行为还是可配置的。 例如项目源码的资源文件的位置可以被自定义,JAR 文件的名字可以被自定义,在开发自定义插件的时候,几乎任何行为可以被裁剪以满足你特定的环境需求。如果你不想遵循约定,Maven 也会允许你自定义默认值来适应你的需求。

                   1.3. 一个一般的接口

Maven 为构建软件提供一个一般的接口之前,每个单独的项目都专门有人来管理一个完全自定义的构建系统。开发人员必须在开发软件之外去学习每个他们要参与的新项目的构建系统的特点。在2001年,构建一个项目如 Turbine 和构建另外一个项目如 Tomcat ,两者方法是完全不同的。如果一个新的进行静态源码分析的源码分析工具面世了,或者如果有人开发了一个新的单元测试框架,每个人都必须放下手头的工作去想办法使这个新东西适应每个项目的自定义构建环境。如何运行单元测试?世界上有一千种不同的答案。构建环境由无数无休止的关于工具和构建程序的争论所描述刻画。Maven 之前的时代是低效率的时代,是“构建工程师”的时代。

现 在,大部分开源开发者已经或者正在使用 Maven 来管理他们新的软件项目。这种转变不仅仅是开发人员从一种构建工具转移到另外一种构建工具,更是开发人员开始为他们的项目采用一种一般的接口。随着软件系统变得越来越模块化,构建系统变得更复杂,而项目的数量更是如火箭般飞速上升。在 Maven 之前,当你想要从 Subversion 签出一个项目如 Apache ActiveMQ Apache ServiceMix ,然后从源码进行构建,你需要为每个项目留出一个小时来理解给它的构建系统。这个项目需要构建什么?需要现在什么类库?把类库放哪里?构建中我该运行什么目标?最好的情况下,理解一个新项目的构建需要几分钟,最坏的情况下(例如 Jakarta 项目的旧的 Servlet API 实现),一个项目的构建特别的困难,以至于花了几个小时以后,新的贡献者也只能编辑源码和编译项目。现在,你只要签出源码,然后运行: mvn install 。虽然 Maven 有很多优点,包括依赖管理和通过插件重用一般的构建逻辑,但它成功的最核心原因是它定义了构建软件的一般的接口。每当你看到一个使用 Maven 的项目如 Apache Wicket ,你就可以假设你能签出它的源码然后使用 mvn install 构建它,没什么好争论的。你知道点火开关在哪里,你知道油门在右边,刹车在左边。

                   1.4. 基于Maven插件的全局性重用

Maven 的核心其实不做什么实际的事情,除了解析一些 XML 文档,管理生命周期与插件之外,它什么也不懂。Maven 被设计成将主要的职责委派给一组 Maven 插件,这些插件可以影响 Maven 生命周期,提供对目标的访问。绝大多数 Maven 的动作发生于 Maven 插件的目标,如编译源码,打包二进制代码,发布站点和其它构建任务。你从 Apache 下载的 Maven 不知道如何打包 WAR 文件,也不知道如何运行单元测试,Maven 大部分的智能是由插件实现的,而插件从 Maven 仓库获得。事实上,第一次你用全新的 Maven 安装运行诸如 mvn install 命令的时候,它会从中央 Maven 仓库下载大部分核心 Maven 插件。这不仅仅是一个最小化 Maven 分发包大小的技巧,这种方式更能让你升级插件以给你项目的构建提高能力。Maven 从远程仓库获取依赖和插件的这一事实允许了构建逻辑的全局性重用。

Maven Surefire 插件是负责运行单元测试的插件。从版本 1.0 发展到目前广泛使用的在 JUnit 基础上增加了 TestNG 测试框架支持的版本。这种发展并没有破坏向后兼容性,如果你使用之前 Surefire 插件编译运行你的 JUnit 3 单元测试,然后你升级到了最新版本的 Surefire 插件,你的测试仍然能成功运行。但是,我们又获得了新的功能,如果你想使用 TestNG 运行单元测试,那么感谢 Surefire 插件的维护者,你已经可以使用 TestNG 了。你也能运行支持注解的 JUnit 4 单元测试。不用升级 Maven 安装或者新装任何软件,你就能获得这些功能。更重要的是,除了 POM 中一个插件的版本号,你不需要更改你项目的任何东西。

这种机制不仅仅适用于 Surefire 插件,项目使用 Compiler 插件进行编译,通过 Jar 插件变成 JAR 文件,还有一些插件生成报告,运行 JRuby Groovy 的代码,以及一些用来向远程服务器发布站点的插件。Maven 将一般的构建任务抽象成插件,同时这些插件得到了很好的维护以及全局的共享,你不需要从头开始自定义你项目的构建系统然后提供支持。你完全可以从 Maven 插件获益,这些插件有人维护,可以从远程仓库下载到。这就是基于 Maven 插件的全局性重用。

                   1.5. 一个“项目”的概念模型

Maven 维护了一个项目的模型,你不仅仅需要把源码编译成字节码,你还需要开发软件项目的描述信息,为项目指定一组唯一的坐标。你要描述项目的的属性。项目的许可证是什么?谁开发这个项目,为这个项目做贡献?这个项目依赖于其它什么项目没有?Maven不仅仅是一个“构建工具”,它不仅仅是在类似于 make Ant 的工具的基础上的改进,它是包含了一组关于软件项目和软件开发的语义规则的平台。这个基于每一个项目定义的模型实现了如下特征:

依赖管理

由于项目是根据一个包含组标识符,构件标识符和版本的唯一的坐标定义的。项目间可以使用这些坐标来声明依赖。

远程仓库

和项目依赖相关的,我们可以使用定义在项目对象模型(POM)中的坐标来创建 Maven 构件的仓库。

全局性构建逻辑重用

插件被编写成和项目模型对象(POM)一起工作,它们没有被设计成操作某一个已知位置的特定文件。一切都被抽象到模型中,插件配置和自定义行为都在模型中进行。

工具可移植性/集成

EclipseNetBeans,和 InteliJ 这样的工具现在有共同的地方来找到项目的信息。在 Maven 出现之前,每个 IDE 都有不同的方法来存储实际上是自定义项目对象模型(POM)的信息。Maven 标准化了这种描述,而虽然每个 IDE 仍然继续维护它的自定义项目文件,但这些文件现在可以很容易的由模型生成。

便于搜索和过滤构件

Nexus 这样的工具允许你使用存储在 POM 中的信息对仓库中的内容进行索引和搜索。

Maven 为软件项目的语义一致性描述的开端提供了一个基础。

                   1.6. MavenAnt的另一种选择么?

当然,Maven Ant 的另一种选择,但是 Apache Ant 继续是一个伟大的,被广泛使用的工具。它已经是多年以来 Java 构建的统治者,而你很容易的在你项目的 Maven 构建中集成 Ant 构建脚本。这是 Maven 项目一种很常见的使用模式。而另一方面,随着越来越多的开源项目转移到 Maven 用它作为项目管理平台,开发人员开始意识到 Maven 不仅仅简化了构建管理任务,它也帮助鼓励开发人员的软件项目使用通用的接口。Maven 不仅仅是一个工具,它更是一个平台,当你只是将 Maven 考虑成 Ant 的另一种选择的时候,你是在比较苹果和橘子。“Maven”包含了很多构建工具以外的东西。

有 一个核心观点使得所有的关于 Maven . AntMaven BuildrMaven Grandle 的争论变得无关紧要。Maven并不是完全根据你构建系统的机制来定义的,它不是为你构建的不同任务编写脚本,它提倡一组标注,一个一般的接口,一个生命周期,一个标准的仓库格式,一个标准的目录布局,等等。它当然也不太在意 POM 的格式正好是 XML 还是 YAML 还是 Ruby。它比这些大得多,Maven 涉及的比构建工具本身多得多。当本书讨论 Maven 的时候,它也设计到支持 Maven 的软件,系统和标准。BuildrIvyGradle,所有这些工具都和 Maven 帮助创建的仓库格式交互,而你可以很容易的使用如 Nexus 这样的工具来支持一个完全由 Buildr 编写的构建。Nexus 将在本书后面介绍。

虽然 Maven 是很多类似工具的另一个选择?但社区需要向前发展,就要看清楚技术是资本经济中不友好的竞争者之间持续的、零和的游戏。这可能是大企业之前相互关联的方式,但是和开源社区的工作方式没太大关系。“谁是胜利者?Ant 还是 Maven”这个大标题没什么建设性意义。如果你非要我们来回答这个问题,我们会很明确的说作为构建的基本技术,Maven Ant 的更好选择;同时,Maven 的边界在持续的移动,Maven 的社区也在持续的是试图找到新的方法,使其更通用,互操作性更好,更易协同工作。Maven 的核心财产是声明性构建,依赖管理,仓库管理,基于插件的高度和重用,但是当前,和开源社区相互协作以降低”企业级构建“的低效率这个目标来比,这些想法的特定实现没那么重要。

                   1.7. 比较MavenAnt

虽然上一节应该已经让你确信本书的作者没有兴趣挑起 Apache Ant Apache Maven 之间的争执,但是我们认识到许多组织必须在 Apache Ant Apache Maven 之间做一个选择。本节我们对比一下这两个工具。

Ant 在构建过程方面十分优秀,它是一个基于任务和依赖的构建系统。每个任务包含一组由 XML 编码的指令。有 copy 任务和 javac 任务,以及 jar 任务。在你使用 Ant 的时候,你为 Ant 提供特定的指令以编译和打包你的输出。看下面的例子,一个简单的 build.xml 文件:

Example 1.1. 一个简单的 Ant build.xml 文件

<project name="my-project" default="dist" basedir=".">

    <description>

        simple example build file

    </description>

  <!-- set global properties for this build -->

  <property name="src" location="src/main/java"/>

  <property name="build" location="target/classes"/>

  <property name="dist"  location="target"/>

 

  <target name="init">

    <!-- Create the time stamp -->

    <tstamp/>

    <!-- Create the build directory structure used by compile -->

    <mkdir dir="${build}"/>

  </target>

 

  <target name="compile" depends="init"

        description="compile the source " >

    <!-- Compile the java code from ${src} into ${build} -->

    <javac srcdir="${src}" destdir="${build}"/>

  </target>

 

  <target name="dist" depends="compile"

        description="generate the distribution" >

    <!-- Create the distribution directory -->

    <mkdir dir="${dist}/lib"/>

 

    <!-- Put everything in ${build} into the MyProject-${DSTAMP}.jar file -->

    <jar jarfile="${dist}/lib/MyProject-${DSTAMP}.jar" basedir="${build}"/>

  </target>

 

  <target name="clean"

        description="clean up" >

    <!-- Delete the ${build} and ${dist} directory trees -->

    <delete dir="${build}"/>

    <delete dir="${dist}"/>

  </target>

</project>

 

在这个简单的 Ant 例子中,你能看到,你需要明确的告诉 Ant 你想让它做什么。有一个包含 javac 任务的编译目标用来将 src/main/java 的源码编译至 target/classes 目录。你必须明确告诉 Ant 你的源码在哪里,结果字节码你想存储在哪里,如何将这些字节码打包成 JAR 文件。虽然最近有些进展以帮助 Ant 减少程序,但一个开发者对 Ant 的感受是用 XML 编写程序语言。

Maven 样例与之前的 Ant 样例做个比较。在 Maven 中,要从 Java 源码创建一个 JAR 文件,你只需要创建一个简单的 pom.xml,将你的源码放在 ${basedir}/src/main/java ,然后从命令行运行 mvn install。下面的样例 Maven pom.xml 文件能完成和之前 Ant 样例所做的同样的事情。

Example 1.2. 一个简单的 Maven pom.xml

<project>

  <modelVersion>4.0.0</modelVersion>

  <groupId>org.sonatype.mavenbook</groupId>

  <artifactId>my-project</artifactId>

  <version>1.0</version>

</project>

 

这就是你 pom.xml 的全部。从命令行运行 mvn install 会处理资源文件,编译源代码,运行单元测试,创建一个 JAR ,然后把这个 JAR 安装到本地仓库以为其它项目提供重用性。不用做任何修改,你可以运行 mvn site ,然后在 target/site 目录找到一个 index.html 文件,这个文件链接了 JavaDoc 和一些关于源代码的报告。

诚然,这是一个最简单的样例项目。一个只包含源代码并且生成一个 JAR 的项目。一个遵循 Maven 的约定,不需要任何依赖和定制的项目。如果我们想要定制行为,我们的 pom.xml 的大小将会增加,在最大的项目中,你能看到一个非常复杂的 Maven POM 的集合,它们包含了大量的插件定制和依赖声明。但是,虽然你项目的 POM 文件变得增大,它们包含的信息与一个差不多大小的基于 Ant 项目的构建文件的信息是完全不同的。Maven POM 包含声明:“这是一个 JAR 项目”,“源代码在 src/main/java 目录”。Ant 构建文件包含显式的指令:“这是一个项目”,“源代码在 src/main/java,“针对这个目录运行 javac ”,“把结果放到 target/classes,“从……创建一个 JAR,等等。Ant 必须的过程必须是显式的,而 Maven 有一些“内置”的东西使它知道源代码在哪里,如何处理它们。

该例中 Ant Maven 的区别是:

Apache Ant

l     Ant 没有正式的约定如一个一般项目的目录结构,你必须明确的告诉 Ant 哪里去找源代码,哪里放置输出。随着时间的推移,非正式的约定出现了,但是它们还没有在产品中模式化。

l     Ant 是程序化的,你必须明确的告诉 Ant 做什么,什么时候做。你必须告诉它去编译,然后复制,然后压缩。

l     Ant 没有生命周期,你必须定义目标和目标之间的依赖。你必须手工为每个目标附上一个任务序列。

Apache Maven

l     Maven 拥有约定,因为你遵循了约定,它已经知道你的源代码在哪里。它把字节码放到 target/classes ,然后在 target 生成一个 JAR 文件。

l     Maven 是声明式的。你需要做的只是创建一个 pom.xml 文件然后将源代码放到默认的目录。Maven 会帮你处理其它的事情。

l     Maven 有一个生命周期,当你运行 mvn install 的时候被调用。这条命令告诉 Maven 执行一系列的有序的步骤,直到到达你指定的生命周期。遍历生命周期旅途中的一个影响就是,Maven 运行了许多默认的插件目标,这些目标完成了像编译和创建一个 JAR 文件这样的工作。

Maven 以插件的形式为一些一般的项目任务提供了内置的智能。如果你想要编写运行单元测试,你需要做的只是编写测试然后放到 ${basedir}/src/test/java ,添加一个对于 TestNG 或者 JUnit 的测试范围依赖,然后运行 mvn test 。如果你想要部署一个 web 应用而非 JAR ,你需要做的是改变你的项目类型为 war ,然后把你文档根目录置为 ${basedir}/src/main/webapp 。当然,你可以用 Ant 做这些事情,但是你将需要从零开始写这些指令。使用 Ant ,你首先需要确定 JUnit JAR 文件应该放在哪里,然后你需要创建一个包含这个 JUnit JAR 文件的 classpath ,然后告诉 Ant 它应该从哪里去找测试源代码,编写一个目标来编译测试源代码为字节码,使用 JUnit 来执行单元测试。

没有诸如 antlibs lvy 等技术的支持(即使有了这些支持技术),Ant 给人感觉是自定义的程序化构建。项目中一组高效的坚持约定的 Maven POM ,相对于 Ant 的配置文件,只有很少的 XML Maven 的另一个优点是它依靠广泛公用的 Maven 插件。所有人使用 Maven Surefire 插件来运行单元测试,如果有人添加了一些针对新的测试框架的支持,你可以仅仅通过在你项目的 POM 中升级某个特定插件的版本来获得新的功能。

使用 Maven 还是 Ant 的决定不是非此即彼的,Ant 在复杂的构建中还有它的位置。如果你目前的构建包含一些高度自定义的过程,或者你已经写了一些 Ant 脚本通过一种明确的方法完成一个明确的过程,而这种过程不适合 Maven 标准,你仍然可以在 Maven 中用这些脚本。作为一个 Maven 的核心插件, Ant 还是可用的。自定义的插件可以用 Ant 来实现,Maven 项目可以配置成在生命周期中运行 Ant 的脚本。

                   1.8. 总结

我们刻意的使这篇介绍保持得简短。 我们略述了一些Maven定义的要点,它们合起来是什么,它是基于什么改进的,同时介绍了其它构建工具。下一章将深入一个简单的项目,看 Maven 如何能够通过最小数量的配置来执行典型的任务。

                   Chapter 2. 安装和运行Maven

2.1. 验证你的Java安装

2.2. 下载Maven

2.3. 安装Maven

2.3.1. 在Mac OSX上安装Maven

2.3.2. 在Microsoft Windows上安装Maven

2.3.3. 在Linux上安装Maven

2.3.4. 在FreeBSD或OpenBSD上安装Maven

2.4. 验证Maven安装

2.5. Maven安装细节

2.5.1. 用户相关配置和仓库

2.5.2. 升级Maven

2.6. 获得Maven帮助

2.7. 使用Maven Help插件

2.7.1. 描述一个Maven插件

2.8. 关于Apache软件许可证

本章包含了在许多不同平台上安装Maven的详细指令。我们会介绍得尽可能详细以减少安装过程中可能出现的问题,而不去假设你已经熟悉安装软件和设置环境变量。本章的唯一前提是你已经安装了恰当的JDK。如果你仅仅对安装感兴趣,读完下载Maven安装Maven后,你就可以直接去看本书其它的部分。如果你对Maven安装的细节感兴趣,本章将会给你提供一个简要的介绍,以及Apache软件许可证,版本2.0的介绍。

                   2.1. 验证你的Java安装

尽管Maven可以运行在Java 1.4上,但本书假设你在至少Java 5上运行。尽管使用你操作系统上最新的稳定版本的JDK。本书的例子在Java 5或者Java 6上都能运行。

% java -version

java version "1.6.0_02"

Java(TM) SE Runtime Environment (build 1.6.0_02-b06)

Java HotSpot(TM) Client VM (build 1.6.0_02-b06, mixed mode, sharing)

Maven能在所有的验证过的JavaTM 兼容的JDK上工作,也包括一些未被验证JDK实现。本书的所有样例是基于Sun官方的JDK编写和测试的。如果你正在使用Linux,你可能需要自己下载SunJDK,并且确定JDK的版本(运行 java -version)。目前Sun已经将Java开源,因此这种情况将来有希望得到改进,将来很有可能Sun JREJDK会被默认安装在Linux上。但直到现在为止,你还是需要自己去下载。

                   2.2. 下载Maven

你可以从Apache Maven项目的web站点下载Mavenhttp://maven.apache.org/download.html.

当你下载Maven的时候,确认你选择了最新版本的Apache Maven。本书书写的时候最新版本是Maven 2.0.9 。如果你不熟悉Apache软件许可证,你需要在使用产品之前去熟悉该许可证的条款。更多的关于Apache软件许可证的信息可以Section 2.8, “关于Apache软件许可证”找到。

                   2.3. 安装Maven

操作系统之间有很大的区别,像Mac OSX 和微软的Windows,而且不同版本Windows之间也有微妙的差别。幸运的是,在所有操作系统上安装Maven的过程,相对来说还是比较直接的。下面的小节概括了在许多操作系统上安装Maven的最佳实践。

                        2.3.1. Mac OSX上安装Maven

你可以从http://maven.apache.org/download.html下载Maven的二进制版本。下载最新的,下载格式最方便你使用的版本。找个地方存放它,并把存档文件解开。如果你把存档文件解压到 /usr/local/maven-2.0.9 ;你可能会需要创建一个符号链接,那样就能更容易使用,当你升级Maven的时候也不再需要改变环境变量。

/usr/local % ln -s maven-2.0.9 maven

/usr/local % export M2_HOME=/usr/local/maven

/usr/local % export PATH=${M2_HOME}/bin:${PATH}

Maven安装好后,你还需要做一些事情以确保它正确工作。你需要将它的 bin 目录(该例中为 /usr/local/maven/bin)添加到你的命令行路径下。你还需要设置 M2_HOME 环境变量,其对应值为Maven的根目录(该例中为 /usr/local/maven)。

Note

OSX TigerOSX Leopard上安装指令是相同的。有报告称Maven 2.0.6正和XCode的预览版本一起发布。如果你安装了XCode,从命令行运行 mvn 检查一下它是否可用。 XCodeMaven安装在了/usr/share/maven。我们强烈建议安装最新版本的Maven 2.0.9,因为随着Maven 2.0.9的发布很多bug被修正了,还有很多改进。

你还需要把 M2_HOME PATH 写到一个脚本里,每次登陆的时候运行这个脚本。把下面的几行加入到 .bash_login

export M2_HOME=/usr/local/maven

export PATH=${M2_HOME}/bin:${PATH}

一旦你把这几行加入到你的环境中,你就可以在命令行运行Maven了。

Note

这些安装指令假设你正运行bash

                        2.3.2. Microsoft Windows上安装Maven

Windows上安装Maven和在Mac OSX上安装Maven十分类似,最主要的区别在于安装位置和设置环境变量。在这里假设Maven安装目录是 c:/Program Files/maven-2.0.9 ,但是,只要你设置的正确的环境变量,把Maven安装到其它目录也一样。当你把Maven解压到安装目录后,你需要设置两个环境变量——PATHM2_M2_HOME。设置这两个环境变量,键入下面的命令:

C:/Users/tobrien > set M2_HOME=c:/Program Files/maven-2.0.9

C:/Users/tobrien > set PATH=%PATH%;%M2_HOME%/bin

在命令行设置环境变量后,你可以在当前会话使用Maven,但是,除非你通过控制面板把它们加入系统变量,你将需要每次登陆系统的时候运行这两行命令。你应该在Microsoft Windows中通过控制面板修改这两个变量。

                        2.3.3. Linux上安装Maven

遵循Section 2.3.1, “在Mac OSX上安装Maven”的步骤,在Linux机器上安装Maven

                        2.3.4. FreeBSDOpenBSD上安装Maven

遵循Section 2.3.1, “在Mac OSX上安装Maven”的步骤,在FreeBSD或者OpenBSD机器上安装MavenT

                   2.4. 验证Maven安装

Maven安装完成后,你可以检查一下它是否真得装好了,通过在命令行运行 mvn -v。如果Maven装好了,你应该能看到类似于下面的输出。

$ mvn -v

Maven 2.0.9

如果你看到了这样的输出,那么Maven已经成功安装了。如果你看不到,而且你的操作系统找不到 mvn 命令,那么确认一下PATHM2_HOME环境变量是否已经正确设置了。

                   2.5. Maven安装细节

Maven的下载文件只有大概1.5 MiB,它能达到如此苗条的大小是因为Maven的内核被设计成根据需要从远程仓库获取插件和依赖。当你开始使用Maven,它会开始下载插件到本地仓库中,就像Section 2.5.1, “用户相关配置和仓库”所描述的那样。对此你可能比较好奇,让我们先很快的看一下Maven的安装目录是什么样子。

/usr/local/maven $ ls -p1

LICENSE.txt

NOTICE.txt

README.txt

bin/

boot/

conf/

lib/

LICENSE.txt 包含了Apache Maven的软件许可证。Section 2.8, “关于Apache软件许可证”会详细描述该许可证。NOTICE.txt 包含了一些Maven依赖的类库所需要的通告及权限。README.txt包含了一些安装指令。 bin/目录包含了运行Mavenmvn脚本。 boot/ 目录包含了一个负责创建Maven运行所需要的类装载器的JAR文件(classwords-1.1.jar)conf/ 目录包含了一个全局的settings.xml文件,该文件用来自定义你机器上Maven的一些行为。如果你需要自定义Maven,更通常的做法是覆写 ~/.m2目录下的settings.xml文件,每个用户都有对应的这个目录。lib/ 目录有了一个包含Maven核心的JAR文件(maven-2.0.9-uber.jar)

                        2.5.1. 用户相关配置和仓库

当你不再仅仅满足于使用Maven,还想扩展它的时候,你会注意到Maven创建了一些本地的用户相关的文件,还有在你home目录的本地仓库。在~/.m2目录下有:

~/.m2/settings.xml

该文件包含了用户相关的认证,仓库和其它信息的配置,用来自定义Maven的行为。

~/.m2/repository/

该目录是你本地的仓库。当你从远程Maven仓库下载依赖的时候,Maven在你本地仓库存储了这个依赖的一个副本。

Note

Unix(OSX)上,可以用 ~ 符号来表示你的home目录,(如~/bin表示/home/tobrien/bin)。在Windows上,我们仍然使用 ~ 来表示你的home目录。在Windows XP上,你的home目录是 C:/Documents and Settings/tobrien,在Windows Vista上,你的home目录是 C:/Users/tobrien。从现在开始,你应该能够理解这种路径表示,并翻译成你操作系统上的对应路径。

                        2.5.2. 升级Maven

如果你遵循Section 2.3.1, “在Mac OSX上安装Maven”Section 2.3.3, “在Linux上安装Maven”,在Mac OSX或者Unix机器上安装了Maven。那么把Maven升级成较新的版本是很容易的事情。只要在当前版本Maven/usr/local/maven-2.0.9)旁边安装新版本的Maven/usr/local/maven-2.future),然后将符号链接 /usr/local/maven /usr/local/maven-2.0.9 改成 /usr/local/maven-2.future即可。你已经将M2_HOME环境变量指向了 /usr/local/maven,因此你不需要更改任何环境变量。

如果你在Windows上安装了Maven,将Maven解压到 c:/Program Files/maven-2.future,然后更新你的M2_HOME环境变量。

                   2.6. 获得Maven帮助

虽然本书的目的是作为一本全面的参考手册,但是仍然会有一些主题我们会不小心遗漏,一些特殊情况和技巧我们也覆盖不到。Maven的核心十分简单,它所做的工作其实都交给插件了。插件太多了,以至于不可能在一本书上全部覆盖。你将会碰到一些本书没有涉及的问题,碰到这种情况,我们建议你在下面的地址去寻找答 案。

http://maven.apache.org

你首先应该看看这里,Mavenweb站点包含了丰富的信息及文档。每个插件都有几页的文档,这里还有一系列“快速开始”的文档,它们是本书内容的十分有 帮助的补充。虽然Maven站点包含了大量信息,它同时也可能让你迷惑沮丧。那里提供了一个自定义的Google搜索框,以用来搜索已有的Maven站点 信息,它能比通用的Google搜索提供更好的结果。

Maven User Mailing List

Maven 用户邮件列表是用户问问题的地方。在你问问题之前,你可以先搜索一下之前的讨论,有可能找到相关问题。问一个已经问过的问题,而不先查一下该问题是否存在了,这种形式不太好。有很多有用的邮件列表归档浏览器,我们发现Nabble最有用。你可以在这里浏览邮件列表:http://www.nabble.com/Maven---Users-f178.html。你也可以按照这里的指令来加入用户邮件列表:http://maven.apache.org/mail-lists.html

http://www.sonatype.com

Sonatype维护了一个本书的在线副本,以及其它Maven相关的指南。

Note

除 去一些专门的Maven贡献者所做的十分优秀的工作,Maven web站点组织的比较糟糕,有很多不完整的,甚至有时候有些误导人的文档片段。在整个Maven社区里,插件文档的一般标准十分缺乏,一些插件的文档十分的丰富,但是另外一些连基本的使用命令都没有。通常你最好是在用户邮件列表里面去搜索下解决方案。

                   2.7. 使用Maven Help插件

本书中,我们将会介绍Maven插件,讲述Maven项目对象模型(POM)文件,settings文件,还有profile。有些时候,你需要一个工具来帮助你理解一些Maven使用的模型,以及某个插件有什么可用的目标。Maven Help插件能让你列出活动的Maven Profile,显示一个实际POMeffective POM),打印实际settingseffective settings),或者列出Maven插件的属性。

Note

如果想看一下POM和插件的概念性介绍,参照第三章:一个简单的Maven项目。

Maven Help 插件有四个目标。前三个目标是—— active-profiles, effective-pom effective-settings —— 描述一个特定的项目,它们必须在项目的目录下运行。最后一个目标—— describe ——相对比较复杂,展示某个插件或者插件目标的相关信息。

help:active-profiles

列出当前构建中活动的Profile(项目的,用户的,全局的)。

help:effective-pom

显示当前构建的实际POM,包含活动的Profile

help:effective-settings

打印出项目的实际settings, 包括从全局的settings和用户级别settings继承的配置。

help:describe

描述插件的属性。它不需要在项目目录下运行。但是你必须提供你想要描述插件的 groupId artifactId

                        2.7.1. 描述一个Maven插件

一旦你开始使用Maven,你会花很多时间去试图获得Maven插件的信息:插件如何工作?配置参数是什么?目标是什么?你会经常使用help:describe 目标来获取这些信息。通过 plugin 参数你可以指定你想要研究哪个插件,你可以传入插件的前缀(如 help 插件就是 maven-help-plugin),或者可以是 groupId:artifact[:version] ,这里 version 是可选的。比如,下面的命令使用 help 插件的 describe 目标来输出 Maven Help 插件的信息。

$ mvn help:describe -Dplugin=help

...

Group Id:  org.apache.maven.plugins

Artifact Id: maven-help-plugin

Version:     2.0.1

Goal Prefix: help

Description:

 

The Maven Help plugin provides goals aimed at helping to make sense out of

    the build environment. It includes the ability to view the effective

    POM and settings files, after inheritance and active profiles

    have been applied, as well as a describe a particular plugin goal to give

    usage information.

...

通过设置 plugin 参数来运行 describe 目标,输出为该插件的Maven坐标,目标前缀,和该插件的一个简要介绍。尽管这些信息非常有帮助,你通常还是需要了解更多的详情。如果你想要 Help 插件输出完整的带有参数的目标列表,只要运行带有参数 full help:describe 目标就可以了,像这样:

$ mvn help:describe -Dplugin=help -Dfull

...

Group Id:  org.apache.maven.plugins

Artifact Id: maven-help-plugin

Version:     2.0.1

Goal Prefix: help

Description:

 

The Maven Help plugin provides goals aimed at helping to make sense out of

    the build environment. It includes the ability to view the effective

    POM and settings files, after inheritance and active profiles

    have been applied, as well as a describe a particular plugin goal to give usage

    information.

 

Mojos:

 

===============================================

Goal: 'active-profiles'

===============================================

Description:

 

Lists the profiles which are currently active for this build.

 

Implementation: org.apache.maven.plugins.help.ActiveProfilesMojo

Language: java

 

Parameters:

-----------------------------------------------

 

[0] Name: output

Type: java.io.File

Required: false

Directly editable: true

Description:

 

This is an optional parameter for a file destination for the output of this mojo...the

listing of active profiles per project.

 

-----------------------------------------------

 

[1] Name: projects

Type: java.util.List

Required: true

Directly editable: false

Description:

 

This is the list of projects currently slated to be built by Maven.

 

-----------------------------------------------

 

This mojo doesn't have any component requirements.

===============================================

 

... removed the other goals ...

该选项能让你查看插件所有的目标及相关参数。但是有时候这些信息显得太多了。这时候你可以获取单个目标的信息,设置 mojo 参数和 plugin 参数。下面的命令列出了 Compiler 插件的 compile 目标的所有信息

$ mvn help:describe -Dplugin=compiler -Dmojo=compile -Dfull

Note

什么? Mojo ?在Maven里面,一个插件目标也被认为是一个 “Mojo”

                   2.8. 关于Apache软件许可证

Apache Maven 是基于 Apache 许可证2.0版 发布的。如果你想阅读该许可证,你可以查阅 ${M2_HOME}/LICENSE.txt 或者从开源发起组织的网站上查阅 http://www.opensource.org/licenses/apache2.0.php

很有可能你正在阅读本书但你又不是律师。如果你想知道 Apache许可证2.0版意味着什么,Apache软件基金会收集了一个很有帮助的,关于许可证的常见问题解答(FAQ):http://www.apache.org/foundation/licence-FAQ.html。这里是对问题“我不是律师,所有这些是什么意思?”的回答。

它允许你:

l     • 自由的下载和使用 Apache 软件,无论是软件的整体还是部分, 也无论是出于个人目的,公司内部目的,还是商业目的。

l     • 在你创建的类库或分发版本里使用 Apache 软件。

它禁止你:

l     在没有正当的权限下重新分发任何源于 Apache 的软件或软件片段。

l     以任何可能声明或暗示基金会认可你的分发版本的形式下使用 Apache 软件基金会拥有的标志。

l     以任何可能声明或暗示你创建了 Apache 软件的形式下使用 Apache 软件基金会拥有的标志。

它要求你:

l     在你重新分发的包含 Apache 软件的软件里,包含一份该许可证的副本。

l     对于任何包含 Apache 软件的分发版本,提供给 Apache软件基金会清楚的权限。

它不要求你:

l     在任何你再次发布的包含Apache软件的版本里,包含Apache软件本身源代码,或者你做的改动的源码。

l     提交你对软件的改动至 Apache 软件基金会(虽然我们鼓励这种反馈)。

               Part I. Maven实战

第一本关于Maven的书是来自O'ReillyMaven开发者笔记,这本书通过一系列步骤介绍Maven。开发者笔记系列书籍背后的想法是,当开发人员和另一个开发人员坐在一起,经历他曾经用来学习和编码的整个思考过程,这样会学得最好。虽然开发者笔记系列成功了,但笔记格式有一个缺点:笔记被设计成“集中关注于目标”,它让你通过一系列步骤来完成某个特定的目标。而有大的参考书,或者“动物”书,提供全面的材料,覆盖了整个的课题。两种书都有优缺点,因此只出版一种书是有问题的。

为了阐明这个问题,考虑如下情形,数万的读者读完开发者笔记后,他们都知道了如何创建一个简单的项目,比方说一个Maven项目从一组源文件创建一个WAR。 但是,当他们想知道更多的细节或者类似于Assembly插件的详细描述的时候,他们会陷入僵局。因为没有关于Maven的写得很好的参考手册,他们需要在Maven站点上搜寻插件文档,或者在一系列邮件列表中不停挑选。当人们真正开始钻研Maven的时候,他们开始在Maven站点上阅读无数的由很多不同的开发人员编写的粗糙的HTML文档,这些开发人员对为插件编写文档有着完全不同的想法:数百的的开发人员有着不同的书写风格,语气以其方言。除去很多好心的志愿者所做的努力,在Maven站点上阅读插件文档,最好的情况下,令人有受挫感,最坏的情况下,成为了抛弃Maven的理由。很多情况下Maven用户感觉“被骗了”因为他们不能从站点文档上找到答案。

虽然第一本Maven开发者笔记吸引了很多Maven 的新用户,为很多人培训了最基本的Maven用例,但同样多的读者,当他们不能找到准确的写得很好的参考材料的时候,感觉到了挫败感。很多年来,缺少可靠的(或者权威的)参考资料阻碍了Maven的发展;这也成了一种Maven用户社区成长的阻碍力量。本书想要改变这种情况,通过提供,第一:在Part I, “Maven实战”中更新原本的Maven开发者笔记,第二:在Part II, “Maven Reference”中第一次尝试提供全面的参考。在你手里的(或者屏幕上的)实际上是二书合一。

本书的这个部分中,我们不会抛弃开发者笔记中的描述性推进方式,这是帮助人们“以实战方式”学习Maven的很有价值的材料。在本书的第一部分中我们“边做边介绍”,然后在Part II, “Maven Reference”中我们填充空白,钻研细节,介绍那些Maven新手不感兴趣的高级话题。Part II, “Maven Reference”可能使用与样例项目无关的一个参考列表和一程序列表,而Part I, “Maven实战”将由实际样例和故事驱动。读完Part I, “Maven实战”后,你将会拥有几个月内开始使用Maven所需要的一切。只有当你需要通过编写定制插件来开始自定义你的项目,或者想了解特定插件细节的时候,才可能需要回到Part II, “Maven Reference”

Table of Contents

3. 一个简单的Maven项目

3.1. 简介

3.1.1. 下载本章的例子

3.2. 创建一个简单的项目

3.3. 构建一个简单的项目

3.4. 简单的项目对象模型 (Project Object Model)

3.5. 核心概念

3.5.1. Maven插件和目标 (Plugins and Goals)

3.5.2. Maven生命周期 (Lifecycle)

3.5.3. Maven坐标 (Coordinates)

3.5.4. Maven仓库(Repositories)

3.5.5. Maven依赖管理 (Dependency Management)

3.5.6. 站点生成和报告 (Site Generation and Reporting)

3.6. 小结

4. 定制一个Maven项目

4.1. 介绍

4.1.1. 下载本章样例

4.2. 定义Simple Weather项目

4.2.1. Yahoo! Weather RSS

4.3. 创建Simple Weather项目

4.4. 定制项目信息

4.5. 添加新的依赖

4.6. Simple Weather源码

4.7. 添加资源

4.8. 运行Simple Weather项目

4.8.1. Maven Exec 插件

4.8.2. 浏览你的项目依赖

4.9. 编写单元测试

4.10. 添加测试范围依赖

4.11. 添加单元测试资源

4.12. 执行单元测试

4.12.1. 忽略测试失败

4.12.2. 跳过单元测试

4.13. 构建一个打包好的命令行应用程序

5. 一个简单的Web应用

5.1. 介绍

5.1.1. 下载本章样例

5.2. 定义这个简单的Web应用

5.3. 创建这个简单的Web应用

5.4. 配置Jetty插件

5.5. 添加一个简单的Servlet

5.6. 添加J2EE依赖

5.7. 小结

6. 一个多模块项目

6.1. 简介

6.1.1. 下载本章样例

6.2. simple-parent 项目

6.3. simple-weather 模块

6.4. simple-webapp 模块

6.5. 构建这个多模块项目

6.6. 运行Web应用

7. 多模块企业级项目

7.1. 简介

7.1.1. 下载本章样例

7.1.2. 多模块企业级项目

7.1.3. 本例中所用的技术

7.2. simple-parent项目

7.3. simple-model模块

7.4. simple-weather模块

7.5. simple-persist模块

7.6. simple-webapp模块

7.7. 运行这个Web应用

7.8. simple-command模块

7.9. 运行这个命令行程序

7.10. 小结

7.10.1. 编写接口项目程序

8. 优化和重构POM

8.1. 简介

8.2. POM清理

8.3. 优化依赖

8.4. 优化插件

8.5. 使用Maven Dependency插件进行优化

8.6. 最终的POM

8.7. 小结

                   Chapter 3. 一个简单的Maven项目

3.1. 简介

3.1.1. 下载本章的例子

3.2. 创建一个简单的项目

3.3. 构建一个简单的项目

3.4. 简单的项目对象模型 (Project Object Model)

3.5. 核心概念

3.5.1. Maven插件和目标 (Plugins and Goals)

3.5.2. Maven生命周期 (Lifecycle)

3.5.3. Maven坐标 (Coordinates)

3.5.4. Maven仓库(Repositories)

3.5.5. Maven依赖管理 (Dependency Management)

3.5.6. 站点生成和报告 (Site Generation and Reporting)

3.6. 小结

                   3.1. 简介

本章我们介绍一个用Maven Archetype插件从空白开始创建的简单项目。当你跟着这个简单项目的开发过程,你会看到这个简单的应用给我们提供了介绍Maven核心概念的机会。

在 你能开始使用Maven做复杂的,多模块的构建之前,我们需要从基础开始。如果你之前用过Maven,你将会注意到这里很好的照顾到了细节。你的构建倾向于“只要能工作”,只有当你需要编写插件来自定义默认行为的时候,才需要深入Maven的细节。另一方面,当你需要深入Maven细节的时候,对 Maven核心概念的彻底理解是至关重要的。本章致力于为你介绍一个尽可能简单的Maven项目,然后介绍一些使Maven成为一个可靠的构建平台的核心概念。读完本章后,你将会对构建生命周期 (build lifecycle)Maven仓库 (repositories),依赖管理 (dependency management)和项目对象模型 (Project Object Model)有一个基本的理解。

                        3.1.1. 下载本章的例子

本章开发了一个十分简单的例子,它将被用来探究Maven的核心概念。如果你跟着本章表述的步骤,你应该不需要下载这些例子来重新创建那些Maven已经生成好的代码。我们将会使用Maven Archetype插件来创建这个简单的项目,本章不会以任何方式修改这个项目。如果你更喜欢通过最终的例子源代码来阅读本章,本章的例子项目和这本书的例子代码可以从这里下载到:http://www.sonatype.com/book/mvn-examples-1.0.zip或者http://www.sonatype.com/book/mvn-examples-1.0.tar.gz。解压存档文件到任何目录下,然后到ch03/目录。在ch03/目录你将看到一个名字为simple/的目录,它包含了本章的源代码。如果你希望在Web浏览器里看这些例子代码,访问http://www.sonatype.com/book/examples-1.0并且点击ch03/目录。

                   3.2. 创建一个简单的项目

开始一个新的Maven项目,在命令行使用Maven Archetype插件。

$ mvn archetype:create -DgroupId=org.sonatype.mavenbook.ch03 /

                                         -DartifactId=simple /

                                         -DpackageName=org.sonatype.mavenbook

[INFO] Scanning for projects...

[INFO] Searching repository for plugin with prefix: 'archetype'.              

[INFO] artifact org.apache.maven.plugins:maven-archetype-plugin: checking for /

       updates from central

[INFO] -----------------------------------------------------------------------

[INFO] Building Maven Default Project

[INFO]    task-segment: [archetype:create] (aggregator-style)

[INFO] --------------------------------------------------------------------

[INFO] [archetype:create]

[INFO] artifact org.apache.maven.archetypes:maven-archetype-quickstart: /

       checking for updates from central

[INFO] Parameter: groupId, Value: org.sonatype.mavenbook.ch03

[INFO] Parameter: packageName, Value: org.sonatype.mavenbook

[INFO] Parameter: basedir, Value: /Users/tobrien/svnw/sonatype/examples

[INFO] Parameter: package, Value: org.sonatype.mavenbook

[INFO] Parameter: version, Value: 1.0-SNAPSHOT

[INFO] Parameter: artifactId, Value: simple

[INFO] * End of debug info from resources from generated POM *

[INFO] Archetype created in dir: /Users/tobrien/svnw/sonatype/examples/simple

mvn Maven2的命令。archetype:create称为一个Maven目标 (goal)。如果你熟悉Apache Ant,一个Maven目标类似于一个Ant目标 (target);它们都描述了将会在构建中完成的工作单元 (unit of work)。而像-Dname=value这样的对是将会被传到目标中的参数,它们使用-D属性这样的形式[1],类似于你通过命令行向Java虚拟机传递系统属性。archetype:create这 个目标的目的通过archetype快速创建一个项目。在这里,一个archetype被定义为“一个原始的模型或者类型,在它之后其它类似的东西与之匹 配;一个原型(prototype)”Maven有许多可用的archetype,从生成一个简单的Swing应用,到一个复杂的Web应用。本章我们 用最基本的archetype来创建一个入门项目的骨架。这个插件的前缀是“archetype”,目标为”create”[1]

我们已经生成了一个项目,看一下Mavensimple目录下创建的目录结构:

simple/

simple/pom.xml

      /src/

      /src/main/

          /main/java

      /src/test/

          /test/java

这个生成的目录遵循Maven标准目录布局,我们之后会去看更多的细节,但是,现在让我们只是尝试了解这些基本的目录。

 

Maven Archtype插件创建了一个与artifactId匹配的目录——simple。这是项目的基础目录。

 

每个项目在文件pom.xml里有它的项目对象模型 (POM)。这个文件描述了这个项目,配置了插件,声明了依赖。

 

我们项目的源码了资源文件被放在了src/main目录下面。在我们简单Java项目这样的情况下,这个目录包含了一下java类和一些配置文件。在其它的项目中,它可能是web应用的文档根目录,或者还放一些应用服务器的配置文件。在一个Java项目中,Java类放在src/main/java下面,而classpath资源文件放在src/main/resources下面。

 

我们项目的测试用例放在src/test下。在这个目录下面,src/test/java存放像使用JUnit或者TestNG这样的Java测试类。目录src/test/resources下存放测试classpath资源文件。

Maven Archtype插件生成了一个简单的类org.sonatype.mavenbook.App,它是一个仅有13行代码的Java,所做的只是在main方法中输出一行消息:

package org.sonatype.mavenbook;

 

/**

 * Hello world!

 *

 */

public class App

{

    public static void main( String[] args )

    {

        System.out.println( "Hello World!" );

    }

}

最简单的Maven archetype生成最简单的Maven项目:一个往标准输出打印“Hello World”的程序。

                   3.3. 构建一个简单的项目

一旦你遵循Section 3.2, “创建一个简单的项目”使用Maven Archetype插件创建了一个项目,你会希望构建并打包这个应用。想要构建打包这个应用,在包含pom.xml的目录下运行mvn install

$ mvn install

[INFO] Scanning for projects...

[INFO] ----------------------------------------------------------------------------

[INFO] Building simple

[INFO]    task-segment: [install]

[INFO] ----------------------------------------------------------------------------

[INFO] [resources:resources]

[INFO] Using default encoding to copy filtered resources.

[INFO] [compiler:compile]

[INFO] Compiling 1 source file to /simple/target/classes

[INFO] [resources:testResources]

[INFO] Using default encoding to copy filtered resources.

[INFO] [compiler:testCompile]

[INFO] Compiling 1 source file to /simple/target/test-classes

[INFO] [surefire:test]

[INFO] Surefire report directory: /simple/target/surefire-reports

 

-------------------------------------------------------

 T E S T S

-------------------------------------------------------

Running org.sonatype.mavenbook.AppTest

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.105 sec

 

Results :

 

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

 

[INFO] [jar:jar]

[INFO] Building jar: /simple/target/simple-1.0-SNAPSHOT.jar

[INFO] [install:install]

[INFO] Installing /simple/target/simple-1.0-SNAPSHOT.jar to /

  ~/.m2/repository/org/sonatype/mavenbook/ch03/simple/1.0-SNAPSHOT/ /

  simple-1.0-SNAPSHOT.jar

你已经创建了,编译了,测试了,打包了,并且安装了(installed)最简单的Maven项目。在命令行运行它以向你自己验证这个程序能工作。

$ java -cp target/simple-1.0-SNAPSHOT.jar org.sonatype.mavenbook.App

Hello World!

                   3.4. 简单的项目对象模型 (Project Object Model)

Maven运行的时候它向项目对象模型(POM)查看关于这个项目的信息。POM回答类似这样的问题:这个项目是什么类型的?这个项目的名称是什么?这个项目的构建有自定义么?这里是一个由Maven Archetype插件的create目标创建的默认的pom.xml文件。

Example 3.1. Simple 项目的 pom.xml 文件

<project xmlns="http://maven.apache.org/POM/4.0.0"

        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

  <modelVersion>4.0.0</modelVersion>

  <groupId>org.sonatype.mavenbook.ch03</groupId>

  <artifactId>simple</artifactId>

  <packaging>jar</packaging>

  <version>1.0-SNAPSHOT</version>

  <name>simple</name>

  <url>http://maven.apache.org</url>

  <dependencies>

    <dependency>

      <groupId>junit</groupId>

      <artifactId>junit</artifactId>

      <version>3.8.1</version>

      <scope>test</scope>

    </dependency>

  </dependencies>

</project>

 

这个pom.xml文件是你将会面对的Maven项目中最基础的POM,一般来说一个POM文件会复杂得多:定义多个依赖,自定义插件行为。最开始的几个元素——groupIdartifactId, packaging, version——Maven的坐标(coordinates),它们唯一标识了一个项目。nameurlPOM提供的描述性元素,它们给人提供了可阅读的名字,将一个项目关联到了项目web站点。最后,dependencies元素定义了一个单独的,测试范围(test-scoped)依赖,依赖于称为JUnit的单元测试框架。这些话题将会Section 3.5, “核心概念”被深入介绍,当前,你所需知道的是,pom.xml是一个让Maven跑起来的文件。

Maven运行的时候,它是根据项目的pom.xml里设置的组合来运行的,一个最上级的POM定义了Maven的安装目录,在这个目录中全局的默认值被定义了,(可能)还有一些用户定义的设置。想要看这个“有效的 (effective)”POM,或者说Maven真正运行根据的POM,在simple项目的基础目录下跑下面的命令。

$ mvn help:effective-pom

一旦你运行了此命令,你应该能看到一个大得多的POM,它暴露了Maven的默认设置

                   3.5. 核心概念

我们已经第一次运行了Maven,是时候介绍一些Maven的核心概念了。在之前的例子中,我们生了一个项目,它包含了一个POM和 一些源代码,它们一起组成了Maven的标准目录布局。之后你用生命周期阶段(phase)作为参数来运行Maven,这个阶段会提示Maven运行一系 列Maven插件的目标。最后,你把Maven构件(artifact)安装(install)到了你本地仓库(repository)。等等?什么是生命周期?什么是“本地仓库”?下面的小结阐述了一些Maven的核心概念。

                        3.5.1. Maven插件和目标 (Plugins and Goals)

在前面一节中,我们用两种类型的命令行参数运行了Maven。第一条命令是一条单个的插件目标,Archetype插件的create目标。Maven第二次运行是一个生命周期阶段 –install。为了运行单个的Maven插件目标,我们使用mvn archetype:create这样的语法,这里archetype是一个插件标识而create是目标标识。当Maven运行一个插件目标,它向标准输出打印出插件标识和目标标识:

$ mvn archetype:create -DgroupId=org.sonatype.mavenbook.ch03 /

                                        -DartifactId=simple /

                                        -DpackageName=org.sonatype.mavenbook

...

[INFO] [archetype:create]

[INFO] artifact org.apache.maven.archetypes:maven-archetype-quickstart: /

       checking for updates from central

...

一个Maven插件是一个单个或者多个目标的集合。Maven插件的例子有一些简单但核心的插件,像Jar插件,它包含了一组创建JAR文 件的目标,Compiler插件,它包含了一组编译源代码和测试代码的目标,或者Surefire插件,它包含一组运行单元测试和生成测试报告的目标。而 其它的,更有专门的插件包括:Hibernate3插件,用来集成流行的持久化框架HibernateJRuby插件,它让你能够让运行ruby称为 Maven构建的一部分或者用Ruby来编写Maven插件。Maven也提供了自定义插件的能力。一个定制的插件可以用Java编写,或者用一些其它的语言如AntGroovybeanshell和之前提到的Ruby

Figure 3.1. 一个插件包含一些目标

 

一个目标是一个明确的任务,它可以作为单独的目标运行,或者作为一个大的构建的一部分和其它目标一起运行。一个目标是Maven中的一个“工作单元(unit of work)”。目标的例子包括Compiler插件中的compile目标,它用来编译项目中的所有源文件,或者Surefire插件中的test目标,用来运行单元测试。目标通过配置属性进行配置,以用来定制行为。例如,Compiler插件的compile目标定义了一组配置参数,它们允许你设置目标JDK版本或者选择是否用编译优化。在之前的例子中,我们通过命令行参数-DgroupId=org.sonatype.mavenbook.ch03-DartifactId=simpleArchetype插件的create目标传入了groupIdartifactId配置参数。我们也向create目标传入了packageName参数,它的值为org.sonatype.mavenbook。如果我们忽略了packageName参数,那么包名的默认值为org.sonatype.mavenbook.ch03

Note

当提到一个插件目标的时候,我们常常用速记符号:pluginId:goalId。例如,当提到Archetype插件的create目标的时候,我们写成archetype:create

目标定义了一些参数,这些参数可以定义一些明智的默认值。在archetype:create这个例子中,我们并没有在命令行中指定这个目标创建什么类型的archetype,我们简单的传入一个groupId和一个artifactId。这是我们对于约定优于配置(convention over configuration)的第一笔。这里create目标的约定,或者默认值,是创建一个简单的项目,叫做Quickstartcreate目标定义了一个配置属性archetypeArtifactId,它有一个默认值为maven-archetype-quickstartQuickstart archetype生成了一个最小项目的躯壳,包括一个POM和一个类。Archetype插件比第一个例子中的样子强大得多,但是这是一个快速开始新项目的不错的方法。在本书的后面,我们将会让你看到Archetype插件可以用来生成复杂如web应用的项目,以及你如何能够使用Archetype插件来定义你自己项目的集合。

Maven的核心对你项目构建中特定的任务几乎毫无所知。就它本身来说,Maven不知道如何编译你的代码,它甚至不知道如何制作一个JAR文 件,它把所有这些任务代理给了Maven插件,像Compiler插件和Jar插件,它们在需要的时候被下载下来并且定时的从Maven中央仓库更新。当 你下载Maven的时候,你得到的是一个包含了基本躯壳的Maven核心,它知道如何解析命令行,管理classpath,解析POM文 件,在需要的时候下载Maven插件。通过保持Compiler插件和Maven核心分离,并且提供更新机制,用户很容易能使用编译器最新的版本。通过这 种方式,Maven插件提供了通用构建逻辑的全局重用性,有不会在构建周期中定义编译任务,有使用了所有Maven用户共享的Compiler插件。如果 有个对Compiler插件的改进,每个使用Maven的项目可以立刻从这种变化中得到好处。(并且,如果你不喜欢这个Compiler插件,你可以用你的实现来覆盖它)。

                        3.5.2. Maven生命周期 (Lifecycle)

上一节中,我们运行的第二个命令是mvn package。 命令行并没有指定一个插件目标,而是指定了一个Maven生命周期阶段。一个阶段是在被Maven称为“构建生命周期”中的一个步骤。生命周期是包含在一个项目构建中的一系列有序的阶段。Maven可以支持许多不同的生命周期,但是最常用的生命周期是默认的Maven生命周期,这个生命周期中一开始的一个阶段是验证项目的基本完整性,最后的一个阶段是把一个项目发布成产品。生命周期的阶段被特地留得含糊,单独的定义为验证(validation),测试 (testing),或者发布(deployment),而他们对不同项目来说意味着不同的事情。例如,打包(package)这个阶段在一个项目里生成一个JAR,它也就意味着“将一个项目打成一个jar”,而在另外一个项目里,打包这个阶段可能生成一个WAR文件。Figure 3.2, “一个生命周期是一些阶段的序列”展示了默认Maven生命周期的简单样子。

Figure 3.2. 一个生命周期是一些阶段的序列

 

插件目标可以附着在生命周期阶段上。随着Maven沿着生命周期的阶段移动,它会执行附着在特定阶段上的目标。每个阶段可能绑定了零个或者多个目标。在之前的小节里,当你运行mvn package,你可能已经注意到了不止一个目标被执行了。检查运行mvn package之后的输出,会注意到那些被运行的各种目标。当这个简单例子到达package阶段的时候,它运行了Jar插件的jar目标。既然我们的简单的quickstart项目(默认)是jar包类型,jar:jar目标被就绑定到了打包阶段。

Figure 3.3. 一个目标绑定到一个阶段

 

我们知道,在包类型为jar的项目中,打包阶段将会创建一个JAR文件。但是,在它之前的目标做什么呢,像compiler:compilesurefire:test?在Maven经过它生命周期中package之前的阶段的时候,这些目标被运行了;Maven执行一个阶段的时候,它首先会有序的执行前面的所有阶段,到命令行指定的那个阶段为止。每个阶段对应了零个或者多个目标。我们没有进行任何插件配置或者定制,所以这个例子绑定了一组标准插件的目标到默认的生命周期。当Maven经过以package为结尾的默认生命周期的时候,下面的目标按顺序被执行:

resources:resources

Resources插件的resources目标绑定到了resources 阶段。这个目标复制src/main/resources下的所有资源和其它任何配置的资源目录,到输出目录。

compiler:compile

Compiler插件的compile目标绑定到了compile 阶段。这个目标编译src/main/java下的所有源代码和其他任何配置的资源目录,到输出目录。

resources:testResources

Resources插件的testResources目标绑定到了test-resources 阶段。这个目标复制src/test/resources下的所有资源和其它任何的配置的测试资源目录,到测试输出目录。

compiler:testCompile

Compiler插件的testCompile目标绑定到了test-compile 阶段。这个目标编译src/test/java下的测试用例和其它任何的配置的测试资源目录,到测试输出目录。

surefire:test

Surefire插件的test目标绑定到了test 阶段。这个目标运行所有的测试并且创建那些捕捉详细测试结果的输出文件。默认情况下,如果有测试失败,这个目标会终止。

jar:jar

Jar插件的jar目标绑定到了package 阶段。这个目标把输出目录打包成JAR文件。

Figure 3.4. 被绑定的目标随着它们阶段的运行而运行

 

总结得来说,当我们运行mvn packageMaven运行到打包为止的所有阶段,在Maven沿着生命周期一步步向前的过程中,它运行绑定在每个阶段上的所有目标。你也可以像下面这样显式的指定一系列插件目标,以得到同样的结果:

mvn resources:resources /

    compiler:compile /

    resources:testResources /

    compiler:testCompile /

    surefire:test /

    jar:jar

运行package阶段能很好的跟踪一个特定的构建中包含的所有目标,它 也允许每个项目使用Maven来遵循一组定义明确的标准。而这个生命周期能让开发人员从一个Maven项目跳到另外一个Maven项目,而不用知道太多每个项目构建的细节。如果你能够构建一个Maven项目,那么你就能构建所有的Maven项目。

                        3.5.3. Maven坐标 (Coordinates)

Archetype插件通过名字为pom.xml的文件创建了一个项目。这就是项目对象模型(POM),一个项目的声明性描述。当Maven运行一个目标的时候,每个目标都会访问定义在项目POM里的信息。当jar:jar目标需要创建一个JAR文件的时候,它通过观察POM来找出这个Jar文件的名字。当compiler:compile任务编译Java源代码为字节码的时候,它通过观察POM来看是否有编译目标的参数。目标在POM的上下文中运行。目标是我们希望针对项目运行的动作,而项目是通过POM定义的。POM为项目命名,提供了项目的一组唯一标识符(坐标),并且通过依赖 (dependencies) ,父 (parents) 和先决条件 (prerequisite) 来定义和其它项目的关系。POM也可以自定义插件行为,提供项目相关的社区和开发人员的信息。

Maven坐标定义了一组标识,它们可以用来唯一标识一个项目,一个依赖,或者Maven POM里的一个插件。看一下下面的POM

Figure 3.5. 一个Maven项目的坐标

 

我们加亮了这个项目的坐标:groupId, artifactId, versionpackaging。这些组合的标识符拼成了一个项目的坐标[2][2]就像任何其它的坐标系统,一个Maven坐标是一个地址,即“空间”里的某个点:从一般到特殊。当一个项目通过依赖,插件或者父项目引用和另外一个项目关联的时候,Maven通过坐标来精确定位一个项目。Maven坐标通常用冒号来作为分隔符来书写,像这样的格式:groupId:artifactId:packaging:version。在上面的pom.xml中,它的坐标可以表示为mavenbook:my-app:jar:1.0-SNAPSHOT.这个符号也适用于项目依赖,我们的项目依赖JUnit3.8.1版本,它包含了一个对junit:junit:jar:3.8.1的依赖。

groupId

d 团体,公司,小组,组织,项目,或者其它团体。团体标识的约定是,它以创建这个项目的组织名称的逆向域名(reverse domain name)开头。来自Sonatype的项目有一个以com.sonatype开头的groupId,而Apache Software的项目有以org.apache开头的groupId

artifactId

groupId下的表示一个单独项目的唯一标识符。

version

一个项目的特定版本。发布的项目有一个固定的版本标识来指向该项目的某一个特定的版本。而正在开发中的项目可以用一个特殊的标识,这种标识给版本加上一个“SNAPSHOT”的标记。

项目的打包格式也是Maven坐标的重要组成部分,但是它不是项目唯一标识符的一个部分。一个项目的groupId:artifactId:version使之成为一个独一无二的项目;你不能同时有一个拥有同样的groupId, artifactIdversion标识的项目。

packaging

项目的类型,默认是jar,描述了项目打包后的输出。类型为jar的项目产生一个JAR文件,类型为war的项目产生一个web应用。

在 其它“Maven化”项目构成的巨大空间中,的这四个元素是定位和使用某个特定项目的关键因素。Maven仓库(repositories)(公共的,私有的,和本地的)是通过这些标识符来组织的。当一个项目被安装到本地的Maven仓库,它立刻能被任何其它的项目所使用。而我们所需要做的只是,在其它项 目用使用Maven的唯一坐标来加入对这个特定构件的依赖。

Figure 3.6. Maven空间是项目的一个坐标系统

 

                        3.5.4. Maven仓库(Repositories)

当 你第一次运行Maven的时候,你会注意到Maven从一个远程的Maven仓库下载了许多文件。如果这个简单的项目是你第一次运行Maven,那么当触 发resources:resource目标的时候,它首先会做的事情是去下载最新版本的Resources插件。在Maven中,构件和插件是在它们被需要的时候从远程的仓库取来的。初始的Maven下载包的大小相当的小(1.8兆),其中一个原因是事实上这个初始Maven不包括很多插件。它只包含了 几近赤裸的最少值,而在需要的时候再从远程仓库去取。Maven自带了一个用来下载Maven核心插件和依赖的远程仓库地址(http://repo1.maven.org/maven2)。

你常常会写这样一个项目,这个项目依赖于一些既不免费也不公开的包。在这种情况下,你需要要么在你组织的网络里安装一个定制的仓库,要么手动的安装这些依赖。默认的远程仓库可以被替换,或者增加一个你组织维护的自定义Maven仓库的引用。有许多现成的项目允许组织管理和维护公共Maven仓库的镜像。

是 什么让Maven仓库成为一个Maven仓库的呢?Maven仓库是通过结构来定义的,一个Maven仓库是项目构件的一个集合,这些构件存储在一个目录结构下面,它们的格式能很容易的被Maven所理解。在一个Maven仓库中,所有的东西存储在一个与Maven项目坐标十分匹配的目录结构中。你可以打 开浏览器,然后浏览中央Maven仓库http://repo1.maven.org/maven2/ 来看这样的结构。你会看到坐标为org.apache.commons:commons-email:1.1的构件能在目录/org/apache/commons/commons-email/1.1/下找到,文件名为commons-email-1.1.jarMaven仓库的标准是按照下面的目录格式来存储构件,相对于仓库的根目录:

/<groupId>/<artifactId>/<version>/<artifactId>-<version>.<packaging>

Maven从远程仓库下载构件和插件到你本机上,存储在你的本地Maven仓库里。一旦Maven已经从远程仓库下载了一个构件,它将永远不需要再下载一次,因为maven会首先在本地仓库查找插件,然后才是其它地方。在Windows XP上,你的本地仓库很可能在C:/Documents and Settings/USERNAME/.m2/repository,在Windows Vista上,会是C:/Users/USERNAME/.m2/repository。在Unix系统上,你的本地仓库在~/.m2/repository。当你创建像前一节创建的简单项目时,install阶段执行一个目标,把你项目的构件安装到你的本地仓库。

在你的本地仓库,你应该可以看到我们的简单项目创建出来的构件。如果你运行mvn install命令,Maven会把我们项目的构件安装到本地仓库。试一下。

$ mvn install

...

[INFO] [install:install]

[INFO] Installing .../simple-1.0-SNAPSHOT.jar to /

       ~/.m2/repository/org/sonatype/mavenbook/simple/1.0-SNAPSHOT/ /

       simple-1.0-SNAPSHOT.jar

...

就像你能从这个命令的输出看到的,Maven把我们项目的JAR文 件安装到了我们的本地Maven仓库。Maven在本地项目中通过本地仓库来共享依赖。如果你开发了两个项目——项目A和项目B——项目B依赖于项目A产 生的构件。当构建项目B的时候,Maven会从本地仓库取得项目A的构件。Maven仓库既是一个从远程仓库下载的构件的缓存,也允许你的项目相互依赖。

                        3.5.5. Maven依赖管理 (Dependency Management)

在本章的simple样例中,Maven处理了JUnit依赖的坐标——junit:junit:3.8.1,指向本地Maven仓库中的/junit/junit/3.8.1/junit-3.8.1.jar。这种基于Maven坐标的定位构件的能力能让我们在项目的POM中定义依赖。如果你检查simple项目的pom.xml文件,你会看到有一个文件中有一个段专门处理dependencies,那里面包含了一个单独的依赖——JUnit

一个复杂的项目将会包含很多依赖,也有可能包含依赖于其它构件的依赖。这是Maven最强大的特征之一,它支持了传递性依赖(transitive dependencies)。假如你的项目依赖于一个库,而这个库又依赖于五个或者十个其它的库(就像Spring或者Hibernate那样)。你不必找出所有这些依赖然后把它们写在你的pom.xml里,你只需要加上你直接依赖的那些库,Maven会隐式的把这些库间接依赖的库也加入到你的项目中。Maven也会处理这些依赖中的冲突,同时能让你自定义默认行为,或者排除一些特定的传递性依赖。

让我们看一下你运行前面的样例的时候那些下载到你本地仓库的依赖。看一下这个目录:~/.m2/repository/junit/junit/3.8.1/。如果你一直跟着本章的样例,那么这里会有文件junit-3.8.1.jar junit-3.8.1.pom,还有Maven用来验证已下载构件准确性的校验和文件。需要注意的是Maven不只是下载JUnitJAR文件,它同时为这个JUnit依赖下载了一个POM文件。Maven同时下载构件和POM文件的这种行为,对Maven支持传递性依赖来说非常重要。

当你把项目的构件安装到本地仓库时,你会发现在和JAR文件同一目录下,Maven发布了一个稍微修改过的pom.xml的版本。存储POM文件在仓库里提供给其它项目了该项目的信息,其中最重要的就是它有哪些依赖。如果项目B依赖于项目A,那么它也依赖于项目A的依赖。当Maven通过一组Maven坐标来处理依赖构件的时候,它也会获取POM,通依赖的POM来寻找传递性依赖。那些传递性依赖就会被添加到当前项目的依赖列表中。

Maven中一个依赖不仅仅是一个JAR。它是一个POM文件,这个POM可能也声明了对其它构件的依赖。这些依赖的依赖叫做传递性依赖,Maven仓库不仅仅存贮二进制文件,也存储了这些构建的元数据(metadata),才使传递性依赖成为可能。下图展现了一个传递性依赖的可能场景。

Figure 3.7. Maven处理传递性依赖

 

在上图中,项目A依赖于项目BC,项目B依赖于项目D,项目C依赖于项目E,但是项目A所需要做的只是定义对BC的依赖。当你的项目依赖于其它的项目,而这些项目又有一些小的依赖时(Hibernate, Apache Struts 或者 Spring Framework),传递性依赖使之变得相当的方便。Maven同时也提供了一种机制,能让你排除一些你不想要的传递性依赖。

Maven也提供了不同的依赖范围(dependency scope)Simple项目的pom.xml包含了一个依赖——junit:junit:jar:3.8.1——范围是test。当一个依赖的范围是test的时候,说明它在Compiler插件运行compile目标的时候是不可用的。它只有在运行compiler:testCompilesurefire:test目标的时候才会被加入到classpath中。

当为项目创建JAR文件的时候,它的依赖不会被捆绑在生成的构件中,他们只是用来编译。当用Maven来创建WAR或者EAR,你可以配置Maven让它在生成的构件中捆绑依赖,你也可以配置Maven,使用provided范围,让它排除WAR文件中特定的依赖。provided范围告诉Maven一个依赖在编译的时候需要,但是它不应该被捆绑在构建的输出中。当你开发web应用的时候provided范围变得十分有用,你需要通过Servlet API来编译你的代码,但是你不希望Servlet APIJAR文件包含在你web应用的WEB-INF/lib目录中。

                        3.5.6. 站点生成和报告 (Site Generation and Reporting)

另外一个Maven的重要特征是,它能生成文档和报告。在simple项目的目录下,运行以下命令:

$ mvn site

这将会运行site生命周期阶段。它不像默认生命周期那样,管理代码生成,操作资源,编译,打包等等。Site生命周期只关心处理在src/site目录下的site内容,还有生成报告。在这个命令运行过之后,你将会在target/site目录下看到一个项目web站点。载入target/site/index.html你会看到项目站点的基本外貌。它包含了一些报告,它们在左手边的导航目录的“项目报告”下面。它也包含了项目相关的信息,依赖和相关开发人员信息,在“项目信息”下面。Simple项目的web站点大部分是空的,因为POM只包含了比较少的信息,只有项目坐标,名称,URL和一个test依赖。

在这个站点上,你会注意到一些默认的报告已经可以访问了,有一个报告详细描述了测试的结果。这个单元测试报告描述了项目中所有单元测试的成功和失败信息。另外一个报告生成了项目APIJavaDocMaven提供了很完整的可配置的报告,像Clover报告检查单元测试覆盖率,JXR报告生成HTML源代码相互间引用,这在代码审查的时候非常有用,PMD报告针对各种编码问题来分析源代码,JDepend报告分析源代码中各个包之间的依赖。通过在pom.xml中配置那些报告被包含在构建中,站点报告就可以被定制了。

                   3.6. 小结

我 们创建了一个simple项目,将其打包为一个Jar,安装到了Maven仓库使之能被其它项目使用,最后生成了带有文档的站点。不写一行代码,不碰一个配置文件,我们就做到了这些。我们花了一些时间来研究Maven核心概念的定义。在下一章,我们将会自定义并修改我们项目的pom.xml文件,加入一些依赖,配置一些单元测试。

 

 

[1] "-D<name>=<value>"这种格式不是Maven定义的,它其实是Java用来设置系统属性的方式,可以通过“java -help”查看Java的解释。Maven的bin目录下的脚本文件仅仅是把属性传入Java而已。

[2] 还有第五个,名为classifier的很少使用的坐标,将在本书后面介绍。现在你尽管可以忽略classifiers。

                   Chapter 4. 定制一个Maven项目

4.1. 介绍

4.1.1. 下载本章样例

4.2. 定义Simple Weather项目

4.2.1. Yahoo! Weather RSS

4.3. 创建Simple Weather项目

4.4. 定制项目信息

4.5. 添加新的依赖

4.6. Simple Weather源码

4.7. 添加资源

4.8. 运行Simple Weather项目

4.8.1. Maven Exec 插件

4.8.2. 浏览你的项目依赖

4.9. 编写单元测试

4.10. 添加测试范围依赖

4.11. 添加单元测试资源

4.12. 执行单元测试

4.12.1. 忽略测试失败

4.12.2. 跳过单元测试

4.13. 构建一个打包好的命令行应用程序

                   4.1. 介绍

本章在上一章所介绍信息的基础上进行开发。 你将创建一个由 Maven Archetype 插件生成的项目,添加一些依赖和一些源代码,并且根据你的需要定制项目。本章最后,你将知道如何使用 Maven 开始创建真正的项目。

                        4.1.1. 下载本章样例

本章我们将开发一个和 Yahoo! Weather web 服务交互的实用程序。虽然没有样例源码你也应该能够理解这个开发过程,但还是推荐你下载本章样例源码以作为参考。本章的样例项目包含在本书的样例代码中,你可以从两个地方下载,http://www.sonatype.com/book/mvn-examples-1.0.zip 或者 http://www.sonatype.com/book/mvn-examples-1.0.tar.gz 。解压存档文件至任意目录,然后到 ch04/ 目录。 在 ch04/ 目录你会看到一个名为 simple-weather 的目录,它包含了本章开发出来的 Maven 项目。如果你想要在浏览器里看样例代码,访问 http://www.sonatype.com/book/examples-1.0 ,然后点击 ch04/ 目录。

                   4.2. 定义Simple Weather项目

在定制本项目之前,让我们退后一步,讨论下这个 simple weather 项目。这个 simple weather 项目是什么? 它是一个被设计成用来示范一些 Maven 特征的样例。 它能代表一类你可能需要构建的应用程序。 这个 simple weather 是一个基本的命令行驱动的应用程序,它接受邮政编码输入,然后从 Yahoo! Weather RSS 源获取数据,然后解析数据并把结果打印到标准输出。我们选择该项目是有许多因素的。 首先,它很直观;用户通过命令行提供输入,程序读取邮政编码,对 Yahoo! Weather 提交请求,之后解析结果,格式化之后输入到屏幕。这个样例是个简单的 main() 函数加上一些相关支持的类;没有企业级框架需要介绍或解释,只有 XML 解析和一些日志语句。其次,它提供很好的机会来介绍一些有趣的类库,如 Velocity, Dom4j Log4j。 虽然本书集中于 Maven ,但我们不会回避那些介绍有趣工具的机会。最后,这是一个能在一章内介绍,开发及部署的样例。

                        4.2.1. Yahoo! Weather RSS

在开始构建这个应用之前,你需要了解一下 Yahoo! Weather RSS 源。该服务是基于以下条款提供的:

“该数据源免费提供给个人和非营利性组织,作为个人或其它非商业用途。 我们要求你提供给 Yahoo! Weather 连接你数据源应用的权限。”

换句话说,如果你考虑集成该数据源到你的商业 web 站点上,请再仔细考虑考虑,该数据源可作为个人或其它非商业性用途。本章我们提倡的使用是个人教育用途。 要了解更多的 Yahoo! Weather 服务条款,请参考 Yahoo! Weather API 文档: http://developer.yahoo.com/weather/

                   4.3. 创建Simple Weather项目

首先,让我们用 Maven Archetype 插件创建这个 simple weather 项目的基本轮廓。运行下面的命令,创建新项目:

$ mvn archetype:create -DgroupId=org.sonatype.mavenbook.ch04 /

                                         -DartifactId=simple-weather /

                                         -DpackageName=org.sonatype.mavenbook /

                                         -Dversion=1.0

[INFO] [archetype:create]

[INFO] artifact org.apache.maven.archetypes:maven-archetype-quickstart: /

       checking for updates from central

[INFO] ------------------------------------------------------------------

[INFO] Using following parameters for creating Archetype: /

       maven-archetype-quickstart:RELEASE

[INFO] ------------------------------------------------------------------

[INFO] Parameter: groupId, Value: org.sonatype.mavenbook.ch04

[INFO] Parameter: packageName, Value: org.sonatype.mavenbook

[INFO] Parameter: basedir, Value: ~/examples

[INFO] Parameter: package, Value: org.sonatype.mavenbook

[INFO] Parameter: version, Value: 1.0

[INFO] Parameter: artifactId, Value: simple-weather

[INFO] *** End of debug info from resources from generated POM ***

[INFO] Archetype created in dir: ~/examples/simple-weather

Maven Archetype 插件创建好了这个项目之后,进入到 simple-weather 目录,看一下 pom.xml。你会看到如下的 XML 文档:

Example 4.1. simple-wheather 项目的初始 POM

<project xmlns="http://maven.apache.org/POM/4.0.0"

         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0

                      http://maven.apache.org/maven-v4_0_0.xsd">

  <modelVersion>4.0.0</modelVersion>

  <groupId>org.sonatype.mavenbook.ch04</groupId>

  <artifactId>simple-weather</artifactId>

  <packaging>jar</packaging>

  <version>1.0</version>

  <name>simple-weather2</name>

  <url>http://maven.apache.org</url>

  <dependencies>

    <dependency>

      <groupId>junit</groupId>

      <artifactId>junit</artifactId>

      <version>3.8.1</version>

      <scope>test</scope>

    </dependency>

  </dependencies>

</project>

 

请注意我们给 archetype:create 目标传入了 version 参数。它覆写了默认值 1.0-SNAPSHOT 。本项目中,正如你从 pom.xml version 元素看到的,我们正在开发 simple-weather 项目的 1.0 版本。

                   4.4. 定制项目信息

在开始编写代码之前,让我们先定制一些项目的信息。我们想要做的是添加一些关于项目许可证,组织以及项目相关开发人员的一些信息。这些都是你期望能在大部分项目中看到的标准信息。下面的文档展示了提供组织信息,许可证信息和开发人员信息的 XML

Example 4.2. pom.xml 添加组织,法律和开发人员信息

<project xmlns="http://maven.apache.org/POM/4.0.0"

         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0

                      http://maven.apache.org/maven-v4_0_0.xsd">

...

 

  <name>simple-weather</name>

  <url>http://www.sonatype.com</url>

 

  <licenses>

    <license>

      <name>Apache 2</name>

      <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>

      <distribution>repo</distribution>

      <comments>A business-friendly OSS license</comments>

    </license>

  </licenses>

 

  <organization>

    <name>Sonatype</name>

    <url>http://www.sonatype.com</url>

  </organization>

 

  <developers>

    <developer>

      <id>jason</id>

      <name>Jason Van Zyl</name>

      <email>jason@maven.org</email>

      <url>http://www.sonatype.com</url>

      <organization>Sonatype</organization>

      <organizationUrl>http://www.sonatype.com</organizationUrl>

      <roles>

        <role>developer</role>

      </roles>

      <timezone>-6</timezone>

    </developer>

  </developers>

...

</project>

 

Example 4.2, “为 pom.xml 添加组织,法律和开发人员信息” 中的省略号是为了使代码清单变得简短。 当你在 pom.xml 中看到 project 元素的开始标签后面跟着 “…” 或者在 project 元素的结束标签前有 “…” ,这说明我们没有展示整个 pom.xml 文件。在上述情况中,licensesorganization developers 元素是加在 dependencies 元素之前的。

                   4.5. 添加新的依赖

Simple weather 应用程序必须要完成以下三个任务:从 Yahoo! Weather 获取 XML 数据,解析 XML 数据,打印格式化的输出至标准输出。为了完成这三个任务,我们需要为项目的 pom.xml 引入一些新的依赖。 为了解析来自 Yahoo! XML 响应,我们将会使用 Dom4J Jaxen ,为了格式化这个命令行程序的输出,我们将会使用 Velocity ,我们还需要加入对 Log4j 的依赖,用来做日志。加入这些依赖之后,我们的 dependencies 元素就成了以下模样:

Example 4.3. 添加 Dom4J, Jaxen, Velocity Log4J 作为依赖

<project>

  [...]

  <dependencies>

    <dependency>

      <groupId>log4j</groupId>

      <artifactId>log4j</artifactId>

      <version>1.2.14</version>

    </dependency>

    <dependency>

      <groupId>dom4j</groupId>

      <artifactId>dom4j</artifactId>

      <version>1.6.1</version>

    </dependency>

    <dependency>

      <groupId>jaxen</groupId>

      <artifactId>jaxen</artifactId>

      <version>1.1.1</version>

    </dependency>

    <dependency>

      <groupId>velocity</groupId>

      <artifactId>velocity</artifactId>

      <version>1.5</version>

    </dependency>

    <dependency>

      <groupId>junit</groupId>

      <artifactId>junit</artifactId>

      <version>3.8.1</version>

      <scope>test</scope>

    </dependency>

  </dependencies>

  [...]

</project>


正如你从上面所看到的,我们在范围为 test JUnit 依赖基础上又加了四个依赖元素。如果你把这些依赖添加到项目的 pom.xml 文件然后运行 mvn install ,你会看到 Maven 下载这些依赖及其它传递性依赖到你的本地 Maven 仓库。 我们如何找这些依赖呢?我们都知道适当的 groupId artifactId 的值吗?有些依赖 ( Log4J) 被广泛使用,以至于每次你需要使用它们的时候你都会记得它们的 groupId artifactId Velocity Dom4J Jaxen 是通过一个十分有用的站点 http://www.mvnrepository.com 来定位的。 该站点提供了针对 Maven 仓库的搜索接口,你可以用它来搜索依赖。 你可以自己测试一下,载入 http://www.mvnrepository.com 然后搜索一些常用的类库,如 Hibernate 或者 Spring Framework 。当你在这上面搜索构件时,它会显示一个 artifactId 和所有 Maven 中央仓库所知道的版本。 点击某个特定的版本后,它会载入一个页面,这个页面就包括了你需要复制到你自己项目 pom.xml 中的依赖元素。 你经常会发现某个特定的类库拥有多于一个的 groupId,这个时候你需要通过 mvnrepository.com 来帮助确定你到底需要怎样配置你的依赖。

                   4.6. Simple Weather源码

Simple Weather 命令行应用程序包含五个 Java 类。

org.sonatype.mavenbook.weather.Main

这个类包含了一个静态的 main() 函数,即系统的入口。

org.sonatype.mavenbook.weather.Weather

Weather 类是个很简单的 Java Bean,它保存了天气报告的地点和其它一些关键元素,如气温和湿度。

org.sonatype.mavenbook.weather.YahooRetriever

YahooRetriever 连接到 Yahoo! Weather 并且返回来自数据源数据的 InputStream

org.sonatype.mavenbook.weather.YahooParser

YahooParser 解析来自 Yahoo! Weather XML,返回 Weather 对象。

org.sonatype.mavenbook.weather.WeatherFormatter

WeatherFormatter 接受 Weather 对象,创建 VelocityContext ,根据 Velocity 模板生成结果。

这里我们不是想要详细阐述样例中的代码,但解释一下程序中使之运行的核心代码还是必要的。我们假设大部分读者已经下载的本书的源码,但也不会忘记那些按着书一步一步往下看的读者。 本小节列出了 simple-weather 项目的类,这些类都放在同一个包下面,org.sonatype.mavenbook.weather

让我们删掉由 archetype:create 生成 App 类和 AppTest 类,然后加入我们新的包。在 Maven 项目中,所有项目的源代码都存储在 src/main/java 目录。在新项目的基础目录下,运行下面的命令:

$ cd src/test/java/org/sonatype/mavenbook

$ rm AppTest.java

$ cd ../../../../../..

$ cd src/main/java/org/sonatype/mavenbook

$ rm App.java

$ mkdir weather

$ cd weather

你已经创建了一个新的包 org.sonatype.mavenbook.weather 。 现在,我们需要把那些类放到这个目录下面。 用你最喜欢的编辑器,创建一个新文件,名字为 Weather.java,内容如下:

Example 4.4. Simple Weather Weather 模型对象

package org.sonatype.mavenbook.weather;

 

 

public class Weather {

  private String city;

  private String region;

  private String country;

  private String condition;

  private String temp;

  private String chill;

  private String humidity;

   

  public Weather() {}

 

  public String getCity() { return city; }

  public void setCity(String city) { this.city = city; }

 

  public String getRegion() { return region; }

  public void setRegion(String region) { this.region = region; }

 

  public String getCountry() { return country; }

  public void setCountry(String country) { this.country = country; }

 

  public String getCondition() { return condition; }

  public void setCondition(String condition) { this.condition = condition; }

 

  public String getTemp() { return temp; }

  public void setTemp(String temp) { this.temp = temp; }

        

  public String getChill() { return chill; }

  public void setChill(String chill) { this.chill = chill; }

 

  public String getHumidity() { return humidity; }

  public void setHumidity(String humidity) { this.humidity = humidity; }

}

 

Weather 类定义了一个简单的 bean ,用来存储由 Yahoo! Weather 数据源解析出来的天气信息。天气数据源提供了丰富的信息,从日出日落时间,到风速和风向。为了让这个例子保持简单, Weather 模型对象只保存温度,湿度和当前天气情况的文字描述等信息。

在同一目录下,创建 Main.java 文件。Main 这个类有一个静态的 main() 函数——样例程序的入口。

Example 4.5. Simple Weather Main

package org.sonatype.mavenbook.weather;

 

import java.io.InputStream;

 

import org.apache.log4j.PropertyConfigurator;

 

 

public class Main {

 

  public static void main(String[] args) throws Exception {

    // Configure Log4J

    PropertyConfigurator.configure(Main.class.getClassLoader()

                                       .getResource("log4j.properties"));

 

    // Read the Zip Code from the Command-line (if none supplied, use 60202)

    int zipcode = 60202;

    try {

      zipcode = Integer.parseInt(args[0]);

    } catch( Exception e ) {}

 

    // Start the program

    new Main(zipcode).start();

  }

 

  private int zip;

 

  public Main(int zip) {

    this.zip = zip;

  }

 

  public void start() throws Exception {

    // Retrieve Data

    InputStream dataIn = new YahooRetriever().retrieve( zip );

 

    // Parse Data

    Weather weather = new YahooParser().parse( dataIn );

 

    // Format (Print) Data

    System.out.print( new WeatherFormatter().format( weather ) );

  }

}

 

上例中的 main() 函数通过获取 classpath 中的资源文件来配置 Log4J ,之后它试图从命令行读取邮政编码。 如果在读取邮政编码的时候抛出了异常,程序会设置默认邮政编码为 60202 。一旦有了邮政编码,它初始化一个 Main 对象,调用该对象的 start() 方法。而 start() 方法会调用 YahooRetriever 来获取天气的 XML 数据。 YahooRetriever 返回一个 InputStreem ,传给 YahooParser YahooParser 解析 XML 数据并返回 Weather 对象。 最后,WeatherFormatter 接受一个 Weather 对象并返回一个格式化的 String ,打印到标准输出。

在相同目录下创建文件 YahooRetriever.java ,内容如下:

Example 4.6. Simple Weather YahooRetriever

package org.sonatype.mavenbook.weather;

 

import java.io.InputStream;

import java.net.URL;

import java.net.URLConnection;

 

import org.apache.log4j.Logger;

 

public class YahooRetriever {

 

  private static Logger log = Logger.getLogger(YahooRetriever.class);

 

  public InputStream retrieve(int zipcode) throws Exception {

    log.info( "Retrieving Weather Data" );

    String url = "http://weather.yahooapis.com/forecastrss?p=" + zipcode;

    URLConnection conn = new URL(url).openConnection();

    return conn.getInputStream();

  }

}

 

这个简单的类打开一个连接到 Yahoo! Weather API URLConnection 并返回一个 InputStream 。 我们还需要在该目录下创建文件 YahooParser.java 用以解析这个数据源。

Example 4.7. Simple Weather YahooParser

package org.sonatype.mavenbook.weather;

 

import java.io.InputStream;

import java.util.HashMap;

import java.util.Map;

 

import org.apache.log4j.Logger;

import org.dom4j.Document;

import org.dom4j.DocumentFactory;

import org.dom4j.io.SAXReader;

 

public class YahooParser {

 

  private static Logger log = Logger.getLogger(YahooParser.class);

 

  public Weather parse(InputStream inputStream) throws Exception {

    Weather weather = new Weather();

 

    log.info( "Creating XML Reader" );

    SAXReader xmlReader = createXmlReader();

    Document doc = xmlReader.read( inputStream );

 

    log.info( "Parsing XML Response" );

    weather.setCity( doc.valueOf("/rss/channel/y:location/@city") );

    weather.setRegion( doc.valueOf("/rss/channel/y:location/@region") );

    weather.setCountry( doc.valueOf("/rss/channel/y:location/@country") );

    weather.setCondition( doc.valueOf("/rss/channel/item/y:condition/@text") );

    weather.setTemp( doc.valueOf("/rss/channel/item/y:condition/@temp") );

    weather.setChill( doc.valueOf("/rss/channel/y:wind/@chill") );

    weather.setHumidity( doc.valueOf("/rss/channel/y:atmosphere/@humidity") );

 

    return weather;

  }

 

  private SAXReader createXmlReader() {

    Map<String,String> uris = new HashMap<String,String>();

        uris.put( "y", "http://xml.weather.yahoo.com/ns/rss/1.0" );

       

    DocumentFactory factory = new DocumentFactory();

    factory.setXPathNamespaceURIs( uris );

       

    SAXReader xmlReader = new SAXReader();

    xmlReader.setDocumentFactory( factory );

    return xmlReader;

  }

}

 

YahooParser 是本例中最复杂的类,我们不会深入 Dom4J 或者 Jaxen 的细节,但是这个类还是需要一些解释。YahooParser parse() 方法接受一个 InputStrem 然后返回一个 Weather 对象。 为了完成这一目标,它需要用 Dom4J 来解析 XML 文档。因为我们对 Yahoo! Weather XML 命名空间的元素感兴趣,我们需要用 createXmlReader() 方法创建一个包含命名空间信息的 SAXReader 。 一旦我们创建了这个 reader 并且解析了文档,得到了返回的 org.dom4j.Document ,只需要简单的使用 XPath 表达式来获取需要的信息,而不是遍历所有的子元素。本例中 Dom4J 提供了 XML 解析功能,而 Jaxen 提供了 XPath 功能。

我们已经创建了 Weather 对象,我们需要格式化输出以供人阅读。在同一目录中创建一个名为 WeatherFormatter.java 的文件。

Example 4.8. Simple Weather WeatherFormatter

package org.sonatype.mavenbook.weather;

 

import java.io.InputStreamReader;

import java.io.Reader;

import java.io.StringWriter;

 

import org.apache.log4j.Logger;

import org.apache.velocity.VelocityContext;

import org.apache.velocity.app.Velocity;

 

public class WeatherFormatter {

 

  private static Logger log = Logger.getLogger(WeatherFormatter.class);

 

  public String format( Weather weather ) throws Exception {

    log.info( "Formatting Weather Data" );

    Reader reader =

      new InputStreamReader( getClass().getClassLoader()

                                 .getResourceAsStream("output.vm"));

    VelocityContext context = new VelocityContext();

    context.put("weather", weather );

    StringWriter writer = new StringWriter();

    Velocity.evaluate(context, writer, "", reader);

    return writer.toString();

  }

}

 

WeatherFormatter 使用 Veloticy 来呈现一个模板。format() 方法接受一个 Weather bean 然后返回格式化好的 String format() 方法做的第一件事是从 classpath 载入名字为 output.vm Velocity 模板。然后我们创建一个 VelocityContext ,它需要一个 Weather 对象来填充。 一个StringWriter被创建用来存放模板生成的结果数据。通过调用 Velocity.evaluate() ,给模板赋值,结果作为 String 返回。

在我们能够运行该样例程序之前,我们需要往 classpath 添加一些资源。

                   4.7. 添加资源

本项目依赖于两个 classpath 资源: Main 类通过 classpath 资源 log4j.preoperties 来配置 Log4J WeatherFormatter 引用了一个在 classpath 中的名为 output.vm Velocity 模板。这两个资源都需要在默认包中(或者 classpath 的根目录)。

为了添加这些资源,我们需要在项目的基础目录下创建一个新的目录—— src/main/resources。 由于任务 archetype:create 没有创建这个目录,我们需要通过在项目的基础目录下运行下面的命令来创建它:

$ cd src/main

$ mkdir resources

$ cd resources

在这个资源目录创建好之后,我们可以加入这两个资源。首先,往目录 resources 加入文件 log4j.properties

Example 4.9. Simple Weather Log4J 配置文件

# Set root category priority to INFO and its only appender to CONSOLE.

log4j.rootCategory=INFO, CONSOLE

 

# CONSOLE is set to be a ConsoleAppender using a PatternLayout.

log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender

log4j.appender.CONSOLE.Threshold=INFO

log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout

log4j.appender.CONSOLE.layout.ConversionPattern=%-4r %-5p %c{1} %x - %m%n

 

这个 log4j.properties 文件简单配置了 Log4J ,使其使用 PatternLayout 往标准输出打印所有日志信息。最后,我们需要创建 output.vm ,它是这个命令行程序用来呈现输出的 Velocity 模板。 在 resources 目录创建 output.vm

Example 4.10. Simple Weather Output Velocity 模板

*********************************

 Current Weather Conditions for:

  ${weather.city}, ${weather.region}, ${weather.country}

 

 Temperature: ${weather.temp}

   Condition: ${weather.condition}

    Humidity: ${weather.humidity}

  Wind Chill: ${weather.chill}

*********************************

这个模板包含了许多对名为 weather 的变量的引用。 这个 weather 变量是传给 WeatherFormatter 的 那个 Weather bean${weather.temp} 语法简化的表示获取并显示 temp 这个bean属性的值。现在我们已经在正确的地方有了我们项目的所有代码,我们可以使用 Maven 来运行这个样例。

 

                   4.8. 运行Simple Weather项目

使用来自 Codehaus Mojo 项目Exec 插件,我们可以运行这个程序。在项目的基础目录下运行以下命令,以运行该程序的 Main 类。

$ mvn install

$ mvn exec:java -Dexec.mainClass=org.sonatype.mavenbook.weather.Main

...

[INFO] [exec:java]

0    INFO  YahooRetriever  - Retrieving Weather Data

134  INFO  YahooParser  - Creating XML Reader

333  INFO  YahooParser  - Parsing XML Response

420  INFO  WeatherFormatter  - Formatting Weather Data

*********************************

 Current Weather Conditions for:

  Evanston, IL, US

 

 Temperature: 45

   Condition: Cloudy

    Humidity: 76

  Wind Chill: 38

*********************************

...

我们没有为 Main 类提供命令行参数,因此程序按照默认的邮编执行——60202。正如你能看到的,我们已经成功的运行了 SImple Weather 命令行工具,从 Yahoo! Weather 获取了一些数据,解析了结果,并且通过 Velocity 格式化了结果数据。我们仅仅写了项目的源代码,往 pom.xml 添加了一些最少的配置。 注意我们这里没有引入“构建过程”。 我们不需要定义如何或哪里让 Java 编译器编译我们的源代码,我们不需要指导构建系统在运行样例程序的时候如何定位二进制文件,我们所需要做的是包含一些依赖,用来定位合适的 Maven 坐标。

                        4.8.1. Maven Exec 插件

Exec 插件允许你运行 Java 类和其它脚本。 它不是 Maven 核心插件,但它可以从 Codehaus Mojo 项目得到。想要查看 Exec 插件的完整描述,运行:

$ mvn help:describe -Dplugin=exec -Dfull

这会列出所有 Maven Exec 插件可用的目标。 Help 插件同时也会列出 Exec 插件的有效参数,如果你想要定制 Exec 插件的行为,传入命令行参数,你应该使用 help:describe 提供的文档作为指南。 虽然 Exec 插件很有用,在开发过程中用来运行测试之外,你不应该依赖它来运行你的应用程序。想要更健壮的解决方案,使用 Maven Assembly 插件,它在Section 4.13, “构建一个打包好的命令行应用程序”中被描述。

                        4.8.2. 浏览你的项目依赖

Exec 插件让我们能够在不往 classpath 载入适当的依赖的情况下,运行这个程序。 在任何其它的构建系统能够中,我们必须复制所有程序依赖到类似于 lib/ 的目录,这个目录包含一个 JAR 文件的集合。那样,我们就必须写一个简单的脚本,在 classpath 中包含我们程序的二进制代码和我们的依赖。 只有那样我们才能运行 java org.sonatype.mavenbook.weather.Main Exec 能做这样的工作是因为 Maven 已经知道如何创建和管理你的 classpath 和你的依赖。

了解你项目的 classpath 包含了哪些依赖是很方便也很有用的。这个项目不仅包含了一些类库如 Dom4JLog4JJaxen,和 Velocity,它同时也引入了一些传递性依赖。 如果你需要找出 classpath 中有什么,你可以使用 Maven Dependency 插件来打印出已解决依赖的列表。要打印出 Simple Weather 项目的这个列表,运行 dependency:resolve 目标。

$ mvn dependency:resolve

...

[INFO] [dependency:resolve]

[INFO]

[INFO] The following files have been resolved:

[INFO]    com.ibm.icu:icu4j:jar:2.6.1 (scope = compile)

[INFO]    commons-collections:commons-collections:jar:3.1 (scope = compile)

[INFO]    commons-lang:commons-lang:jar:2.1 (scope = compile)

[INFO]    dom4j:dom4j:jar:1.6.1 (scope = compile)

[INFO]    jaxen:jaxen:jar:1.1.1 (scope = compile)

[INFO]    jdom:jdom:jar:1.0 (scope = compile)

[INFO]    junit:junit:jar:3.8.1 (scope = test)

[INFO]    log4j:log4j:jar:1.2.14 (scope = compile)

[INFO]    oro:oro:jar:2.0.8 (scope = compile)

[INFO]    velocity:velocity:jar:1.5 (scope = compile)

[INFO]    xalan:xalan:jar:2.6.0 (scope = compile)

[INFO]    xerces:xercesImpl:jar:2.6.2 (scope = compile)

[INFO]    xerces:xmlParserAPIs:jar:2.6.2 (scope = compile)

[INFO]    xml-apis:xml-apis:jar:1.0.b2 (scope = compile)

[INFO]    xom:xom:jar:1.0 (scope = compile)

正如你能看到的,我们项目拥有一个很大的依赖集合。虽然我们只是为四个类库引入了直接的依赖,看来我们实际共引入了15个依赖。 Dom4J 依赖于 Xerces XML 解析器 API Jaxen 依赖于 Xalan,后者也就在 classpath 中可用了。 Dependency 插件将会打印出最终的你项目编译所基于的所有依赖的组合。如果你想知道你项目的整个依赖树,你可以运行 dependency:tree 目标。

$ mvn dependency:tree

...

[INFO] [dependency:tree]

[INFO] org.sonatype.mavenbook.ch04:simple-weather:jar:1.0

[INFO] +- log4j:log4j:jar:1.2.14:compile

[INFO] +- dom4j:dom4j:jar:1.6.1:compile

[INFO] |  /- xml-apis:xml-apis:jar:1.0.b2:compile

[INFO] +- jaxen:jaxen:jar:1.1.1:compile

[INFO] |  +- jdom:jdom:jar:1.0:compile

[INFO] |  +- xerces:xercesImpl:jar:2.6.2:compile

[INFO] |  /- xom:xom:jar:1.0:compile

[INFO] |     +- xerces:xmlParserAPIs:jar:2.6.2:compile

[INFO] |     +- xalan:xalan:jar:2.6.0:compile

[INFO] |     /- com.ibm.icu:icu4j:jar:2.6.1:compile

[INFO] +- velocity:velocity:jar:1.5:compile

[INFO] |  +- commons-collections:commons-collections:jar:3.1:compile

[INFO] |  +- commons-lang:commons-lang:jar:2.1:compile

[INFO] |  /- oro:oro:jar:2.0.8:compile

[INFO] +- org.apache.commons:commons-io:jar:1.3.2:test

[INFO] /- junit:junit:jar:3.8.1:test

...

如果你还不满足,或者想要查看完整的依赖踪迹,包含那些因为冲突或者其它原因而被拒绝引入的构件,打开 Maven 的调试标记运行:

$ mvn install -X

...

[DEBUG] org.sonatype.mavenbook.ch04:simple-weather:jar:1.0 (selected for null)

[DEBUG]   log4j:log4j:jar:1.2.14:compile (selected for compile)

[DEBUG]   dom4j:dom4j:jar:1.6.1:compile (selected for compile)

[DEBUG]     xml-apis:xml-apis:jar:1.0.b2:compile (selected for compile)

[DEBUG]   jaxen:jaxen:jar:1.1.1:compile (selected for compile)

[DEBUG]     jaxen:jaxen:jar:1.1-beta-6:compile (removed - causes a cycle in the graph)

[DEBUG]     jaxen:jaxen:jar:1.0-FCS:compile (removed - causes a cycle in the graph)

[DEBUG]     jdom:jdom:jar:1.0:compile (selected for compile)

[DEBUG]     xml-apis:xml-apis:jar:1.3.02:compile (removed - nearer found: 1.0.b2)

[DEBUG]     xerces:xercesImpl:jar:2.6.2:compile (selected for compile)

[DEBUG]     xom:xom:jar:1.0:compile (selected for compile)

[DEBUG]       xerces:xmlParserAPIs:jar:2.6.2:compile (selected for compile)

[DEBUG]       xalan:xalan:jar:2.6.0:compile (selected for compile)

[DEBUG]       xml-apis:xml-apis:1.0.b2.

[DEBUG]       com.ibm.icu:icu4j:jar:2.6.1:compile (selected for compile)

[DEBUG]   velocity:velocity:jar:1.5:compile (selected for compile)

[DEBUG]     commons-collections:commons-collections:jar:3.1:compile (selected for compile)

[DEBUG]     commons-lang:commons-lang:jar:2.1:compile (selected for compile)

[DEBUG]     oro:oro:jar:2.0.8:compile (selected for compile)

[DEBUG]   junit:junit:jar:3.8.1:test (selected for test)

从调试输出我们看到一些依赖管理系统工作的内部信息。 你在这里看到的是项目的依赖树。 Maven 正打印出你项目的所有的依赖,以及这些依赖的依赖(还有依赖的依赖的依赖)的完整的 Maven 坐标。 你能看到 simple-weather 依赖于 jaxen jaxen 依赖于 xom xom 接着依赖于 icu4j 。从该输出你能看到 Maven 正在创建一个依赖图,排除重复,解决不同版本之间的冲突。如果你的依赖有问题,通常在 dependency:tree 所生成的列表基础上更深入一点会有帮助;开启调试输出允许你看到 Maven 工作时的依赖机制。

                   4.9. 编写单元测试

Maven 内建了对单元测试的支持,测试是 Maven 默认生命周期的一部分。让我们给 Simple Weather 项目添加一些单元测试。 首先,在 src/test/java 下面创建包 org.sonatype.mavenbook.weather

$ cd src/test/java

$ cd org/sonatype/mavenbook

$ mkdir -p weather/yahoo

$ cd weather/yahoo

目前,我们将会创建两个单元测试。 第一个单元测试会测试 YahooParser ,第二个会测试 WeatherFormatter。 在 weather 包中,创建一个带有一以下内容的文件,名称为 YahooParserTest.java

Example 4.11. Simple Weather YahooParserTest 单元测试

package org.sonatype.mavenbook.weather.yahoo;

 

import java.io.InputStream;

 

import junit.framework.TestCase;

 

import org.sonatype.mavenbook.weather.Weather;

import org.sonatype.mavenbook.weather.YahooParser;

 

public class YahooParserTest extends TestCase {

 

  public YahooParserTest(String name) {

    super(name);

  }

 

  public void testParser() throws Exception {

    InputStream nyData =

      getClass().getClassLoader().getResourceAsStream("ny-weather.xml");

    Weather weather = new YahooParser().parse( nyData );

    assertEquals( "New York", weather.getCity() );

    assertEquals( "NY", weather.getRegion() );

    assertEquals( "US", weather.getCountry() );

    assertEquals( "39", weather.getTemp() );

    assertEquals( "Fair", weather.getCondition() );

    assertEquals( "39", weather.getChill() );

    assertEquals( "67", weather.getHumidity() );

  }

}

 

YahooParserTest 继承了 JUnit 定义的 TestCase 类。 它遵循了 JUnit 测试的惯例模式:一个构造函数接受一个单独的 String 参数并调用父类的构造函数,还有一系列以“test”开头的公有方法,做为单元测试被调用。我们定义了一个单独的测试方法, testParser ,通过解析一个值已知的 XML 文档来测试 YahooParser 。 测试 XML 文档命名为 ny-weather.xml ,从 classpath 载入。我们将在Section 4.11, “添加单元测试资源”添加测试资源。 在我们这个 Maven 项目的目录布局中,文件 ny-weather.xml 可以从包含测试资源的目录—— ${basedir}/src/test/resources ——中找到,路径为 org/sonatype/mavenbook/weather/yahoo/ny-weather.xml 。 该文件作为一个 InputStream 被读入,传给 YahooParser parse() 方法。 parse() 方法返回一个 Weather 对象,该对象通过一系列 由 TestCase 定义的 assertEquals() 调用而被测试。

在同一目录下创建一个名为 WeatherFormatterTest.java 的文件。

Example 4.12. Simple Weather WeatherFormatterTest 单元测试

package org.sonatype.mavenbook.weather.yahoo;

 

import java.io.InputStream;

 

import org.apache.commons.io.IOUtils;

 

import org.sonatype.mavenbook.weather.Weather;

import org.sonatype.mavenbook.weather.WeatherFormatter;

import org.sonatype.mavenbook.weather.YahooParser;

 

import junit.framework.TestCase;

 

public class WeatherFormatterTest extends TestCase {

 

  public WeatherFormatterTest(String name) {

    super(name);

  }

 

  public void testFormat() throws Exception {

    InputStream nyData =

      getClass().getClassLoader().getResourceAsStream("ny-weather.xml");

    Weather weather = new YahooParser().parse( nyData );

    String formattedResult = new WeatherFormatter().format( weather );

    InputStream expected =

      getClass().getClassLoader().getResourceAsStream("format-expected.dat");

    assertEquals( IOUtils.toString( expected ).trim(), formattedResult.trim() );

  }

}

 

该项目中的第二个单元测试测试 WeatherFormatter。 和 YahooParserTest 一样,WeatherFormatter 同样也继承 JUnit TestCase 类。 这个单独的测试通过单元测试的 classpath ${basedir}/src/test/resources org/sonatype/mavenbook/weather/yahoo 目录读取同样的测试资源文件。我们将会在Section 4.11, “添加单元测试资源”添加测试资源。 WeatherFormatterTest 首先调用 YahooParser 解析出 Weather 对象,然后用 WeatherFormatter 格式化这个对象。我们的期望输出被存储在一个名为 format-expected.dat 的文件中,该文件存放在和 ny-weather.xml 同样的目录中。要比较测试输出和期望输出,我们将期望输出作为 InputStream 读入,然后使用 Commons IO IOUtils 类来把文件转化为 String 。 然后使用 assertEquals() 比较这个 String 和测试输出。

                   4.10. 添加测试范围依赖

在类 WeatherFormatterTest 中我们用了一个来自于 Apache Commons IO 的工具—— IOUtils 类。 IOUtils 提供了许多很有帮助的静态方法,能帮助让很多工作摆脱繁琐的 I/O 操作。在这个单元测试中我们使用了 IOUtils.toString() 来复制 classpath 中资源 format.expected.dat 中的数据至 String。 不用 Commons IO 我们也能完成这件事情,但是那需要额外的六七行代码来处理像 InputStreamReader StringWriter 这样的对象。我们使用 Commons IO 的主要原因是,能有理由添加对 Commons IO 的测试范围依赖。

测试范围依赖是一个只在测试编译和测试运行时在 classpath 中有效的依赖。如果你的项目是以 war 或者 ear 形式打包的,测试范围依赖就不会被包含在项目的打包输出中。要添加一个测试范围依赖,在你项目的 dependencies 小节中添加如下 dependency 元素。

Example 4.13. 添加一个测试范围依赖

<project>

  ...

  <dependencies>

    ...

    <dependency>

      <groupId>org.apache.commons</groupId>

      <artifactId>commons-io</artifactId>

      <version>1.3.2</version>

      <scope>test</scope>

    </dependency>

    ...

  </dependencies>

</project>

 

当你往 pom.xml 中添加了这个依赖以后,运行 mvn dependency:resolve 你会看到 commons-io 出现在在依赖列表中,范围是 test 。在我们可以运行该项目的单元测试之前,我们还需要做一件事情。 那就是创建单元测试依赖的 classpath 资源。测试范围依赖将在 9.4.1节 “依赖范围” 中详细解释。

                   4.11. 添加单元测试资源

一个单元测试需要访问针对测试的一组资源。 通常你需要在测试 classpath 中存储一些包含期望结果的文件,以及包含模拟输入的文件。在本项目中,我们为 YahooParserTest 准备了一个名为 ny-weather.xml 的测试 XML 文档,还有一个名为 format-expected.dat 的文件,包含了 WeatherFormatter 的期望输出。

要添加测试资源,你需要创建目录 src/test/resources 。 这是 Maven 寻找测试资源的默认目录。在你的项目基础目录下运行下面的命令以创建该目录。

$ cd src/test

$ mkdir resources

$ cd resources

当你创建好这个资源目录之后,在资源目录下创建一个名为 format-expected.dat 的文件。

Example 4.14. Simple Weather WeatherFormatterTest 期望输出

*********************************

 Current Weather Conditions for:

  New York, NY, US

 

 Temperature: 39

   Condition: Fair

    Humidity: 67

  Wind Chill: 39

*********************************

 

这个文件应该看起来很熟悉了,它和你用 Maven Exec 插件运行 Simple Weather 项目得到的输出是一样的。你需要在资源目录添加的第二个文件是 ny-weather.xml

Example 4.15. Simple Weather YahooParserTest XML 输入

<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>

<rss version="2.0" xmlns:yweather="http://xml.weather.yahoo.com/ns/rss/1.0"

     xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#">

 <channel>

 <title>Yahoo! Weather - New York, NY</title>

 <link>http://us.rd.yahoo.com/dailynews/rss/weather/New_York__NY/</link>

 <description>Yahoo! Weather for New York, NY</description>

 <language>en-us</language>

 <lastBuildDate>Sat, 10 Nov 2007 8:51 pm EDT</lastBuildDate>

 

 <ttl>60</ttl>

 <yweather:location city="New York" region="NY" country="US" />

 <yweather:units temperature="F" distance="mi" pressure="in" speed="mph" />

 <yweather:wind chill="39" direction="0" speed="0" />

 <yweather:atmosphere humidity="67" visibility="1609" pressure="30.18"

                      rising="1" />

  <yweather:astronomy sunrise="6:36 am" sunset="4:43 pm" />

  <image>

 <title>Yahoo! Weather</title>

 

 <width>142</width>

 <height>18</height>

 <link>http://weather.yahoo.com/</link>

 <url>http://l.yimg.com/us.yimg.com/i/us/nws/th/main_142b.gif</url>

 </image>

 <item>

 <title>Conditions for New York, NY at 8:51 pm EDT</title>

 

  <geo:lat>40.67</geo:lat>

 <geo:long>-73.94</geo:long>

  <link>http://us.rd.yahoo.com/dailynews/rss/weather/New_York__NY//</link>

 <pubDate>Sat, 10 Nov 2007 8:51 pm EDT</pubDate>

 <yweather:condition text="Fair" code="33" temp="39"

                     date="Sat, 10 Nov 2007 8:51 pm EDT" />

 <description><![CDATA[

<img src="http://l.yimg.com/us.yimg.com/i/us/we/52/33.gif" /><br />

 <b>Current Conditions:</b><br />

 Fair, 39 F<BR /><BR />

 <b>Forecast:</b><BR />

  Sat - Partly Cloudy. High: 45 Low: 32<br />

  Sun - Sunny. High: 50 Low: 38<br />

 <br />

 ]]></description>

 <yweather:forecast day="Sat" date="10 Nov 2007" low="32" high="45"

                    text="Partly Cloudy" code="29" />

 

<yweather:forecast day="Sun" date="11 Nov 2007" low="38" high="50"

                   text="Sunny" code="32" />

  <guid isPermaLink="false">10002_2007_11_10_20_51_EDT</guid>

 </item>

</channel>

</rss>

 

该文件包含了一个给 YahooParserTest 用的 XML 文档。有了这个文件,我们不用从 Yahoo! Weather 获取 XML 响应就能测试 YahooParser 了。

                   4.12. 执行单元测试

既然你的项目已经有单元测试了,那么让它们运行起来吧。你不必为了运行单元测试做什么特殊的事情, test 阶段是 Maven 生命周期中常规的一部分。 当你运行 mvn package 或者 mvn install 的时候你也运行了测试。 如果你想要运行到 test 阶段为止的所有生命周期阶段,运行 mvn test

$ mvn test

...

[INFO] [surefire:test]

[INFO] Surefire report directory: ~/examples/simple-weather/target/surefire-reports

 

-------------------------------------------------------

 T E S T S

-------------------------------------------------------

Running org.sonatype.mavenbook.weather.yahoo.WeatherFormatterTest

0    INFO  YahooParser  - Creating XML Reader

177  INFO  YahooParser  - Parsing XML Response

239  INFO  WeatherFormatter  - Formatting Weather Data

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.547 sec

Running org.sonatype.mavenbook.weather.yahoo.YahooParserTest

475  INFO  YahooParser  - Creating XML Reader

483  INFO  YahooParser  - Parsing XML Response

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.018 sec

 

Results :

 

Tests run: 2, Failures: 0, Errors: 0, Skipped: 0

从命令行运行 mvn test 使 Maven 执行到 test 阶段为止的所有生命周期阶段。 Maven Surefire 插件有一个 test 目标,该目标被绑定在了 test 阶段。 test 目标执行项目中所有能在 src/test/java 找到的并且文件名与 **/Test*.java**/*Test.java **/*TestCase.java 匹配的所有单元测试。 在本例中,你能看到 Surefire 插件的 test 目标执行了 WeatherFormatterTest YahooParserTest 。 在 Maven Surefire 插件执行 JUnit 测试的时候,它同时也在 ${basedir}/target/surefire-reports 目录下生成 XML 和常规文本报告。如果你的测试失败了,你可以去查看这个目录,里面有你单元测试生成的异常堆栈信息和错误信息。

                        4.12.1. 忽略测试失败

通常,你会开发一个带有很多失败单元测试的系统。 如果你正在实践测试驱动开发(TDD),你可能会使用测试失败来衡量你离项目完成有多远。 如果你有失败的单元测试,但你仍然希望产生构建输出,你就必须告诉 Maven 让它忽略测试失败。 当 Maven 遇到一个测试失败,它默认的行为是停止当前的构建。如果你希望继续构建项目,即使 Surefire 插件遇到了失败的单元测试,你就需要设置 Surefire testFailureIgnore 这个配置属性为 true

Example 4.16. 忽略单元测试失败

<project>

  [...]

  <build>

    <plugins>

      <plugin>

        <groupId>org.apache.maven.plugins</groupId>

        <artifactId>maven-surefire-plugin</artifactId>

        <configuration>

          <testFailureIgnore>true</testFailureIgnore>

        </configuration>

      </plugin>

    </plugins>

  </build>

  [...]

</project>

 

该插件文档 (http://maven.apache.org/plugins/maven-surefire-plugin/test-mojo.html) 说明,这个参数声明为一个表达式:

Example 4.17. 插件参数表达式

       testFailureIgnore  Set this to true to ignore a failure during testing. Its use is NOT RECOMMENDED, but quite convenient on occasion.

 

    * Type: boolean

    * Required: No

    * Expression: ${maven.test.failure.ignore}

      

 

这个表达式可以从命令行通过 -D 参数设置。

$ mvn test -Dmaven.test.failure.ignore=true

                        4.12.2. 跳过单元测试

你可能想要配置 Maven 使其完全跳过单元测试。可能你有一个很大的系统,单元测试需要花好多分钟来完成,而你不想在生成最终输出前等单元测试完成。你可能正工作在一个遗留系统上面,这个系统有一系列的失败的单元测试,你可能仅仅想要生成一个 JAR 而不是去修复所有的单元测试。 Maven 提供了跳过单元测试的能力,只需要使用 Surefire 插件的 skip 参数。 在命令行,只要简单的给任何目标添加 maven.test.skip 属性就能跳过测试:

$ mvn install -Dmaven.test.skip=true

...

[INFO] [compiler:testCompile]

[INFO] Not compiling test sources

[INFO] [surefire:test]

[INFO] Tests are skipped.

...

Surefire 插件到达 test 目标的时候,如果 maven.test.skip 设置为 true ,它就会跳过单元测试。 另一种配置 Maven 跳过单元测试的方法是给你项目的 pom.xml 添加这个配置。 你需要为你的 build 添加 plugin 元素。

Example 4.18. 跳过单元测试

<project>

  [...]

  <build>

    <plugins>

      <plugin>

        <groupId>org.apache.maven.plugins</groupId>

        <artifactId>maven-surefire-plugin</artifactId>

        <configuration>

          <skip>true</skip>

        </configuration>

      </plugin>

    </plugins>

  </build>

  [...]

</project>

 

                   4.13. 构建一个打包好的命令行应用程序

Section 4.12, “执行单元测试”,我们使用 Maven Exec 插件运行了 Simple Weather 应用程序。虽然 Maven Exec 能运行程序并且产生输出,你不能就把 Maven 当成是你程序运行的容器。如果你把这个命令行程序分发给其他人,你大概就需要分发一个 JAR 或者一个 ZIP 存档文件或者 TAR 压缩过的 GZIP 文件。下面的小节介绍了使用 Maven Assembly 插件的预定义装配描述符生成一个可分发的 JAR 文件的过程,该文件包含了项目的二进制文件和所有的依赖。

Maven Assembly 插件是一个用来创建你应用程序特有分发包的插件。 你可以使用 Maven Assembly 插件以你希望的任何形式来装配输出,只需定义一个自定义的装配描述符。后面的章节我们会说明如何创建一个自定义装配描述符,为 Simple Weather 应用程序生成一个更复杂的存档文件。 本章我们将会使用预定义的 jar-with-dependencies 格式。 要配置 Maven Assembly 插件,我们需要在 pom.xml 中的 build 配置中添加如下的 plugin 配置。

Example 4.19. 配置 Maven 装配描述符

<project>

  [...]

  <build>

    <plugins>

      <plugin>

        <artifactId>maven-assembly-plugin</artifactId>

        <configuration>

          <descriptorRefs>

            <descriptorRef>jar-with-dependencies</descriptorRef>

          </descriptorRefs>

        </configuration>

      </plugin>

    </plugins>

  </build>

  [...]

</project>

 

添加好这些配置以后,你可以通过运行 mvn assembly:assembly 来构建这个装配。

$ mvn install assembly:assembly

...

[INFO] [jar:jar]

[INFO] Building jar: ~/examples/simple-weather/target/simple-weather-1.0.jar

[INFO] [assembly:assembly]

[INFO] Processing DependencySet (output=)

[INFO] Expanding: /

       .m2/repository/dom4j/dom4j/1.6.1/dom4j-1.6.1.jar into /

       /tmp/archived-file-set.1437961776.tmp

[INFO] Expanding: .m2/repository/commons-lang/commons-lang/2.1/commons-lang-2.1.jar /

       into /tmp/archived-file-set.305257225.tmp

... (Maven Expands all dependencies into a temporary directory) ...

[INFO] Building jar: /

       ~/examples/simple-weather/target/simple-weather-1.0-jar-with-dependencies.jar

target/simple-weather-1.0-jar-with-dependencies.jar 装配好之后,我们可以在命令行重新运行 Main 类。在你项目的基础目录下运行以下命令:

$ cd target

$ java -cp simple-weather-1.0-jar-with-dependencies.jar org.sonatype.mavenbook.weather.Main 10002

0    INFO  YahooRetriever  - Retrieving Weather Data

221  INFO  YahooParser  - Creating XML Reader

399  INFO  YahooParser  - Parsing XML Response

474  INFO  WeatherFormatter  - Formatting Weather Data

*********************************

 Current Weather Conditions for:

  New York, NY, US

 

 Temperature: 44

   Condition: Fair

    Humidity: 40

  Wind Chill: 40

*********************************

jar-with-dependencies 格式创建一个包含所有 simple-weather 项目的二进制代码以及所有依赖解压出来的二进制代码的 JAR 文件。这个略微非常规的格式产生了一个 9 MiB 大小的 JAR 文件,包含了大概 5290 个类。 但是它确实给那些使用 Maven 开发的应用程序提供了一个易于分发的格式。本书的后面,我们会说明如何创建一个自定义的装配描述符来生成一个更标准的分发包。

                   Chapter 5. 一个简单的Web应用

5.1. 介绍

5.1.1. 下载本章样例

5.2. 定义这个简单的Web应用

5.3. 创建这个简单的Web应用

5.4. 配置Jetty插件

5.5. 添加一个简单的Servlet

5.6. 添加J2EE依赖

5.7. 小结

                   5.1. 介绍

本章我们使用 Maven Archetype 插件创建一个简单的 web 应用程序。我们将会在一个名为 Jetty Servlet 容器中运行这个 web 应用程序,同时添加一些依赖,编写一个简单的 Servlet,并且生成一个 WAR 文件。 本章最后,你将能够开始使用 Maven 来提高你开发 web 应用程序的速度。

                        5.1.1. 下载本章样例

本章的样例是通过 Maven Archetype 插件生成的。虽然没有样例源码你也应该能够理解这个开发过程,但还是推荐你下载样例源码作为参考。 本章的样例项目包含在本书的样例代码中,你可以从两个地方下载,http://www.sonatype.com/book/mvn-examples-1.0.zip 或者 http://www.sonatype.com/book/mvn-examples-1.0.tar.gz 。解开存档文件至任意目录,然后到 ch05/ 目录。 在 ch05/ 目录你会看到一个名为 simple-webapp/ 的目录,它包含了本章开发出来的 Maven 项目。如果你想要在浏览器里看样例代码,访问 http://www.sonatype.com/book/examples-1.0 ,然后点击 ch05/ 目录。

                   5.2. 定义这个简单的Web应用

我们已经有意的使本章关注于一个简单 Web 应用(POWA)—— 一个 servlet 和一个 JSP 页面。 在接下来的二十多页中,我们不会告诉你如何开发你的 Struts 2TapestyWicketJSF,或者 Waffle 应用,我们也不会涉及到集成诸如 PlexusGuice 或者 Spring Framework 之类的 IoC 容器。 本章的目标是展示给你看开发 web 应用的时候 Maven 提供的基本设备,不多,也不少。本书的后面,我们将会看一下开发两个 web 应用,一个使用了 HibernateVelocity Spring Framework,另外一个使用了 Plexus

                   5.3. 创建这个简单的Web应用

创建你的 web 应用程序项目,运行 mvn archetype:create ,加上参数 artifactId groupId。 指定 archetypeArtifactId maven-archetype-webapp。 如此便创建了恰到好处的目录结构和 Maven POM

~/examples$ mvn archetype:create -DgroupId=org.sonatype.mavenbook.ch05 /

                                                             -DartifactId=simple-webapp /

                                                             -DpackageName=org.sonatype.mavenbook /

                                                             -DarchetypeArtifactId=maven-archetype-webapp

[INFO] [archetype:create]

[INFO] ----------------------------------------------------------------------------

[INFO] Using following parameters for creating Archetype: maven-archetype-webapp:RELEASE

[INFO] ----------------------------------------------------------------------------

[INFO] Parameter: groupId, Value: org.sonatype.mavenbook.ch05

[INFO] Parameter: packageName, Value: org.sonatype.mavenbook

[INFO] Parameter: basedir, Value: ~/examples

[INFO] Parameter: package, Value: org.sonatype.mavenbook

[INFO] Parameter: version, Value: 1.0-SNAPSHOT

[INFO] Parameter: artifactId, Value: simple-webapp

[INFO] ********************* End of debug info from resources from generated POM *******

[INFO] Archetype created in dir: ~/examples/simple-webapp

Maven Archetype 插件创建好了项目之后,切换目录至 simple-web 后看一下 pom.xml 。 你会看到如下的 XML 文档:

Example 5.1. simple-web 项目的初始 POM

<project xmlns="http://maven.apache.org/POM/4.0.0"

         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0

         http://maven.apache.org/maven-v4_0_0.xsd">

  <modelVersion>4.0.0</modelVersion>

  <groupId>org.sonatype.mavenbook.ch05</groupId>

  <artifactId>simple-webapp</artifactId>

  <packaging>war</packaging>

  <version>1.0-SNAPSHOT</version>

  <name>simple-webapp Maven Webapp</name>

  <url>http://maven.apache.org</url>

  <dependencies>

    <dependency>

      <groupId>junit</groupId>

      <artifactId>junit</artifactId>

      <version>3.8.1</version>

      <scope>test</scope>

    </dependency>

  </dependencies>

  <build>

    <finalName>simple-webapp</finalName>

  </build>

</project>

 

注意 packaging 元素包含的值是 war 。这种打包类型配置让 Maven WAR 文件的形式生成一个 web 应用。一个打包类型为 war 的项目,将会在 target/ 目录创建一个 WAR 文件,这个文件的默认名称是 ${artifactId}-${version}.war 。对于这个项目, 默认的 WAR 文件是 target/simple-webapp-1.0-SNAPSHOT.war 。在这个 simple-webapp 项目中,我们已经通过在项目的构建配置中加入 finalName 元素来自定义这个生成的 WAR 文件的名称。根据 simple-webapp finalName package 阶段生成的 WAR 文件为 target/simple-webapp.war

                   5.4. 配置Jetty插件

在你已经编译,测试并且打包了你的 web 应用之后,你会想要将它部署到一个 servlet 容器中,然后测试一下由 Maven Archetype 插件创建的 index.jsp 。通常情况下,你需要下载 Jetty 或者 Apache Tomcat,解压分发包,复制你的应用程序 WAR 文件至 webapps/ 目录,然后启动你的容器。现在,实现同样的目的,你不再需要做这些事情。 取而代之的是,你可以使用 Maven Jetty 插件在 Maven 中运行你的 web 应用。为此,你需要在项目的 pom.xml 中配置 Maven Jetty 插件。在你项目的构建配置中添加如下插件元素:

Example 5.2. 配置 Jetty 插件

<project>

  [...]

  <build>

    <finalName>simple-webapp</finalName>

    <plugins>

      <plugin>

        <groupId>org.mortbay.jetty</groupId>

        <artifactId>maven-jetty-plugin</artifactId>

      </plugin>

    </plugins>

  </build>

  [...]

</project>

 

在项目的 pom.xml中配置好 Maven Jetty 插件之后,你就可以调用 Jetty 插件的 Run 目标在 Jetty Servlet 容器中启动你的 web 应用。如下运行 mvn jetty:run

~/examples$ mvn jetty:run

...

[INFO] [jetty:run]

[INFO] Configuring Jetty for project: simple-webapp Maven Webapp

[INFO] Webapp source directory = /

       /Users/tobrien/svnw/sonatype/examples/simple-webapp/src/main/webapp

[INFO] web.xml file = /

       /Users/tobrien/svnw/sonatype/examples/simple-webapp/src/main/webapp/WEB-INF/web.xml

[INFO] Classes = /Users/tobrien/svnw/sonatype/examples/simple-webapp/target/classes

2007-11-17 22:11:50.532::INFO:  Logging to STDERR via org.mortbay.log.StdErrLog

[INFO] Context path = /simple-webapp

[INFO] Tmp directory =  determined at runtime

[INFO] Web defaults = org/mortbay/jetty/webapp/webdefault.xml

[INFO] Web overrides =  none

[INFO] Webapp directory = /

       /Users/tobrien/svnw/sonatype/examples/simple-webapp/src/main/webapp

[INFO] Starting jetty 6.1.6rc1 ...

2007-11-17 22:11:50.673::INFO:  jetty-6.1.6rc1

2007-11-17 22:11:50.846::INFO:  No Transaction manager found - if your webapp requires one,/

       please configure one.

2007-11-17 22:11:51.057::INFO:  Started SelectChannelConnector@0.0.0.0:8080

[INFO] Started Jetty Server

Maven 启动了 Jetty Servlet 容器之后,在浏览器中载入 URL http://localhost:8080/simple-webapp/ Archetype 生成的简单页面 index.jsp 没什么价值;它包含了一个文本为“Hello World!”的二级标题。 Maven 认为 web 应用程序的文档根目录为 src/main/webapp 。这个目录就是存放 index.jsp 的目录 。 index.jsp 的内容为 Example 5.3, “src/main/webapp/index.jsp 的内容”

Example 5.3. src/main/webapp/index.jsp 的内容

<html>

  <body>

    <h2>Hello World!</h2>

  </body>

</html>

 

src/main/webapp/WEB-INF 目录中我们会找到可能是最小的 web 应用程序描述符 web.xml

Example 5.4. src/main/webapp/WEB-INF/web.xml 的内容

<!DOCTYPE web-app PUBLIC

 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"

 "http://java.sun.com/dtd/web-app_2_3.dtd" >

 

<web-app>

  <display-name>Archetype Created Web Application</display-name>

</web-app>

 

                   5.5. 添加一个简单的Servlet

一个只有一个单独的 JSP 页面而没有任何配置好的 servlet web 应用程序基本是没用的。 让我们为这个应用添加一个简单的 servlet ,同时为 pom.xml web.xml 做些改动以支持这个变化。 首先,我们需要在目录 src/main/java 下创建一个名为 org.sonatype.mavenbook.web 的新的包。

$ mkdir -p src/main/java/org/sonatype/mavenbook/web

$ cd src/main/java/org/sonatype/mavenbook/web

包创建好之后,切换目录至 src/main/java/org/sonatype/mavenbook/web ,创建一个名为 SimpleServlet.java servlet 类,代码如下:

Example 5.5. SimpleServlet

package org.sonatype.mavenbook.web;

 

import java.io.*;

import javax.servlet.*;                                                         

import javax.servlet.http.*;

 

public class SimpleServlet extends HttpServlet {

    public void doGet(HttpServletRequest request,

                      HttpServletResponse response)

        throws ServletException, IOException {

   

        PrintWriter out = response.getWriter();

        out.println( "SimpleServlet Executed" );

        out.flush();

        out.close();

    }

}

 

我们的 SimpleServlet 仅此而已,一个往响应 Writer 打印一条简单信息的 servlet 。为了把这个 servlet 添加到你的 web 应用,并且使其与请求路径匹配,需要添加如下的 servlet servlet-mapping 元素至你项目的 web.xml 文件。 文件 web.xml 可以在目录 src/main/webapp/WEB-INF 中找到。

Example 5.6. 匹配 Simple Servlet

<!DOCTYPE web-app PUBLIC

 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"

 "http://java.sun.com/dtd/web-app_2_3.dtd" >

 

<web-app>

  <display-name>Archetype Created Web Application</display-name>

  <servlet>

    <servlet-name>simple</servlet-name>

    <servlet-class>org.sonatype.mavenbook.web.SimpleServlet</servlet-class>

  </servlet>

  <servlet-mapping>

    <servlet-name>simple</servlet-name>

    <url-pattern>/simple</url-pattern>

  </servlet-mapping>

</web-app>

 

一切文件就绪,准备测试这个 servlet src/main/java 下的类,以及 web.xml 已经被更新了。在启动 Jetty 插件之前,运行 mvn compile 以编译你的项目:

~/examples$ mvn compile

...

[INFO] [compiler:compile]

[INFO] Compiling 1 source file to ~/examples/ch05/simple-webapp/target/classes

[INFO] ------------------------------------------------------------------------

[ERROR] BUILD FAILURE

[INFO] ------------------------------------------------------------------------

[INFO] Compilation failure

 

~/ch05/simple-webapp/src/main/java/org/sonatype/mavenbook/web/SimpleServlet.java:[4,0] /

  package javax.servlet does not exist

 

~/ch05/simple-webapp/src/main/java/org/sonatype/mavenbook/web/SimpleServlet.java:[5,0] /

  package javax.servlet.http does not exist

 

~/ch05/simple-webapp/src/main/java/org/sonatype/mavenbook/web/SimpleServlet.java:[7,35] /

  cannot find symbol

  symbol: class HttpServlet

  public class SimpleServlet extends HttpServlet {

 

~/ch05/simple-webapp/src/main/java/org/sonatype/mavenbook/web/SimpleServlet.java:[8,22] /

  cannot find symbol

  symbol  : class HttpServletRequest

  location: class org.sonatype.mavenbook.web.SimpleServlet

 

~/ch05/simple-webapp/src/main/java/org/sonatype/mavenbook/web/SimpleServlet.java:[9,22] /

  cannot find symbol

  symbol  : class HttpServletResponse

  location: class org.sonatype.mavenbook.web.SimpleServlet

 

~/ch05/simple-webapp/src/main/java/org/sonatype/mavenbook/web/SimpleServlet.java:[10,15] /

  cannot find symbol

  symbol  : class ServletException

  location: class org.sonatype.mavenbook.web.SimpleServlet

编译失败了,因为你的 Maven 项目没有对 Servlet API 的依赖。 在下一节,我们会为你项目的 POM 添加 Servlet API

                   5.6. 添加J2EE依赖

为了编写一个 servlet ,我们需要添加 Servlet API 作为项目依赖。 Servlet 规格说明是一个 JAR 文件,它能从 Sun Microsystems 的站点下载到 http://java.sun.com/products/servlet/download.html JAR 文件下载好之后你需要把它安装到位于 ~/.m2/repository Maven 本地仓库。你必须为所有 Sun Microsystems 维护的 J2EE API 重复同样的过程,包括 JNDIJDBCServletJSPJTA, 以及其它。如果你不想这么做因为觉得这样太无聊了,其实不只有你这么认为。 幸运的是,有一种更简单的方法来下载所有这些类库并安装到本地仓库 —— Apache Geronimo 的独立的开源实现。

很多年以来,获取 Servlet 规格说明 JAR 文件的唯一方式是从 Sun Microsystems 下载。你必须到 Sun web 站点,同意并且点击它的许可证协议,这样才能访问 Servlet JAR。这是必须的,因为 Sun 的规格说明 JAR 文件并没有使用一个允许再次分发的许可证。很多年来编写一个 Servlet 或者使用 JDBC 之前你必须手工下载 Sun 的构件。 这很乏味并且令人恼火,直到 Apache Geronimo 项目创建了很多通过 Sun 认证的企业级规格说明实现。 这些规格说明 JAR 是按照 Apache 软件许可证版本 2.0 发布的,该许可证允许对源代码和二进制文件进行免费的再次分发。现在,对你的程序来说,从 Sun Microsystems 下载的 Servlet API JAR 和从 Apache Geronimo 项目下载的 JAR 没什么大的差别。 它们同样都通过了 Sun Microsystems 的严格的一个测试兼容性工具箱(TCK)

添加像 JSP API 或者 Servlet API 这样的依赖现在很简单明了了,不再需要你从 web 站点手工下载 JAR 文件然后再安装到本地仓库。关键是你必须知道去哪里找,使用什么 groupIdartifactId,和 version 来引用恰当的 Apache Geronimo 实现。给 pom.xml 添加如下的依赖元素以添加对 Servlet 规格说明 API 的依赖。.

Example 5.7. 添加 Servlet 2.4 规格说明作为依赖

<project>

  [...]

  <dependencies>

    [...]

    <dependency>

      <groupId>org.apache.geronimo.specs</groupId>

      <artifactId>geronimo-servlet_2.4_spec</artifactId>

      <version>1.1.1</version>

      <scope>provided</scope>

    </dependency>

  </dependencies>

  [...]

</project>

 

所有 Apache Geronimo 规格说明的实现的 groupId 都是 org.apache.geronimo.specs 。这个 artifactId 包含了你熟悉的规格说明的版本号;例如,如果你要引入 Servlet 2.3 规格说明,你将使用的 artifactId geronimo-servlet_2.3_spec ,如果你想要引入 Servlet 2.4 规格说明,那么你的 artifactId 将会是 geronimo-servlet_2.4_spec 。你必须看一下 Maven 的公共仓库来决定使用什么版本。最好使用某个规格说明实现的最新版本。 如果你在寻找某个特定的 Sun 规格说明对应的 Apache Geronimo 项目,我们已经在附录归纳了一个可用规格说明的列表。

这里还有必要指出的是我们的这个依赖使用了 provided 范围。这个范围告诉 Maven jar 文件已经由容器“提供”了,因此不再需要包含在 war 中。

如果你对在这个简单 web 应用编写自定义 JSP 标签感兴趣,你将需要添加对 JSP 2.0 规格说明的依赖。使用以下配置来加入这个依赖。

Example 5.8. 添加 JSP 2.0 规格说明作为依赖

<project>

  [...]

  <dependencies>

    [...]

    <dependency>

      <groupId>org.apache.geronimo.specs</groupId>

      <artifactId>geronimo-jsp_2.0_spec</artifactId>

      <version>1.1</version>

      <scope>provided</scope>

    </dependency>

  </dependencies>

  [...]

</project>

 

在添加好这个 Servlet 规格说明依赖之后,运行 mvn clean install ,然后运行 mvn jetty:run

[tobrien@t1 simple-webapp]$ mvn clean install

...

[tobrien@t1 simple-webapp]$ mvn jetty:run

[INFO] [jetty:run]

...

2007-12-14 16:18:31.305::INFO:  jetty-6.1.6rc1

2007-12-14 16:18:31.453::INFO:  No Transaction manager found - if your webapp requires one,/

           please configure one.

2007-12-14 16:18:32.745::INFO:  Started SelectChannelConnector@0.0.0.0:8080

[INFO] Started Jetty Server

到此为止,你应该能够获取 SimpleServlet 的输出。在命令行,你可以使用 curl 在标准输出打印 servlet 的输出。

~/examples$ curl http://localhost:8080/simple-webapp/simple

SimpleServlet Executed

                   5.7. 小结

阅读完本章之后,你应该能够启动一个简单的 web 应用程序。本章并没有详细描述用很多不同的方式来创建一个完整的 web 引用,其它章节对那些包含很多流行 web 框架和技术的项目提供了更全面的介绍

                   Chapter 6. 一个多模块项目

6.1. 简介

6.1.1. 下载本章样例

6.2. simple-parent 项目

6.3. simple-weather 模块

6.4. simple-webapp 模块

6.5. 构建这个多模块项目

6.6. 运行Web应用

                   6.1. 简介

本章,我们创建一个结合了前两章样例的多模块项目。???中开发的simple-weather代码将会与Chapter 5, 一个简单的Web应用中定义的simple-webapp结合以创建一个新的web应用,它获取天气预报信息然后显示在web页面上。本章最后,你能将够使用Maven开发复杂的,多模块项目。

                        6.1.1. 下载本章样例

该样例中开发的多模块项目包含了???Chapter 5, 一个简单的Web应用中项目的修改的版本,我们不会再使用Maven Archetype插件来生成这个多模块项目。我们强烈建议当你在阅读本章内容的时候,下载样例代码作为一个补充参考。本章的样例项目包含在本书的样例代码中,你可以从两个地方下载,http://www.sonatype.com/book/mvn-examples-1.0.zip或者http://www.sonatype.com/book/mvn-examples-1.0.tar.gz。解开存档文件至任意目录,然后到ch06/目录。在ch06/目录你会看到一个名为simple-parent/的目录,它包含了本章开发出来的多模块Maven项目。在这个simple-parent/项目目录中,你会看到一个pom.xml,以及两个子模块目录simple-weather/simple-webapp/。如果你想要在浏览器里看样例代码,访问http://www.sonatype.com/book/examples-1.0,然后点击ch06/目录。

                   6.2. simple-parent 项目

一个多模块项目通过一个父POM引用一个或多个子模块来定义。在simple-parent/目录中你能找到一个父POM(也称为顶层POM)为simple-parent/pom.xml

Example 6.1. simple-parent 项目的 POM

<project xmlns="http://maven.apache.org/POM/4.0.0"

  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0

                      http://maven.apache.org/maven-v4_0_0.xsd">

  <modelVersion>4.0.0</modelVersion>

 

  <groupId>org.sonatype.mavenbook.ch06</groupId>

  <artifactId>simple-parent</artifactId>

  <packaging>pom</packaging>

  <version>1.0</version>

  <name>Chapter 6 Simple Parent Project</name>

 

  <modules>

    <module>simple-weather</module>

    <module>simple-webapp</module>

  </modules>

 

  <build>

    <pluginManagement>

      <plugins>

        <plugin>

          <groupId>org.apache.maven.plugins</groupId>

          <artifactId>maven-compiler-plugin</artifactId>

          <configuration>

            <source>1.5</source>

            <target>1.5</target>

          </configuration>

        </plugin>

      </plugins>

   </pluginManagement>

  </build>

 

  <dependencies>

    <dependency>

      <groupId>junit</groupId>

      <artifactId>junit</artifactId>

      <version>3.8.1</version>

      <scope>test</scope>

    </dependency>

  </dependencies>

</project>

 

 

注意simple-parent定义了一组Maven坐标:groupIdorg.sonatype.mavenbookartifactIdsimple-parentversion1.0。这个父项目不像之前的项目那样创建一个JAR或者一个WAR,它仅仅是一个引用其它Maven项目的POM。像simple-parent这样仅仅提供项目对象模型的项目,正确的的打包类型是pompom.xml中下一部分列出了项目的子模块。这些模块在modules元素中定义,每个modules元素对应了一个simple-parent/目录下的子目录。Maven知道去这些子目录寻找pom.xml文件,并且,在构建的simp-parent的时候,它会将这些子模块包含到要构建的项目中。

最后,我们定义了一些将会被所有子模块继承的设置。simple-parentbuild部分配置了所有Java编译的目标是Java 5 JVM。因为compiler插件默认绑定到了生命周期,我们就可以使用pluginManagement部分来配置。我们将会在后面的章节详细讨论pluginManagement,区分为默认的插件提供配置和真正的绑定插件是很容易的。dependencies元素将JUnit 3.8.1添加为一个全局的依赖。build配置和dependencies都会被所有的子模块继承。使用POM继承允许你添加一些全局的依赖如JUnitLog4J

                   6.3. simple-weather 模块

我们要看的第一个子模块是simple-weather子模块。这个子模块包含了所有用来与Yahoo! weather信息源交互的类。

Example 6.2. simple-weather 模块的 POM

<project xmlns="http://maven.apache.org/POM/4.0.0"

  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0

                      http://maven.apache.org/maven-v4_0_0.xsd">

  <modelVersion>4.0.0</modelVersion>

  <parent>

    <groupId>org.sonatype.mavenbook.ch06</groupId>

    <artifactId>simple-parent</artifactId>

    <version>1.0</version>

  </parent>

  <artifactId>simple-weather</artifactId>

  <packaging>jar</packaging>

 

  <name>Chapter 6 Simple Weather API</name>

 

  <build>

    <pluginManagement>

      <plugins>

        <plugin>

          <groupId>org.apache.maven.plugins</groupId>

          <artifactId>maven-surefire-plugin</artifactId>

          <configuration>

            <testFailureIgnore>true</testFailureIgnore>

          </configuration>

        </plugin>

      </plugins>

    </pluginManagement>

  </build>

 

  <dependencies>

    <dependency>

      <groupId>log4j</groupId>

      <artifactId>log4j</artifactId>

      <version>1.2.14</version>

    </dependency>

    <dependency>

      <groupId>dom4j</groupId>

      <artifactId>dom4j</artifactId>

      <version>1.6.1</version>

    </dependency>

    <dependency>

      <groupId>jaxen</groupId>

      <artifactId>jaxen</artifactId>

      <version>1.1.1</version>

    </dependency>

    <dependency>

      <groupId>velocity</groupId>

      <artifactId>velocity</artifactId>

      <version>1.5</version>

    </dependency>

    <dependency>

      <groupId>org.apache.commons</groupId>

      <artifactId>commons-io</artifactId>

      <version>1.3.2</version>

      <scope>test</scope>

    </dependency>

  </dependencies>

</project>

 

simple-weatherpom.xml文件中我们看到该模块使用一组Maven坐标引用了一个父POMsimple-weather的父POM通过一个值为org.sonatype.mavenbookgroupId,一个值为simple-parentartifactId,以及一个值为1.0version来定义。注意子模块中我们不再需要重新定义groupIdversion,它们都从父项目继承了。

Example 6.3. WeatherService

package org.sonatype.mavenbook.weather;

 

import java.io.InputStream;

 

public class WeatherService {

 

    public WeatherService() {}

 

    public String retrieveForecast( String zip ) throws Exception {

        // Retrieve Data

        InputStream dataIn = new YahooRetriever().retrieve( zip );

 

        // Parse Data

        Weather weather = new YahooParser().parse( dataIn );

 

        // Format (Print) Data

        return new WeatherFormatter().format( weather );

    }

}

 

WeatherService类在src/main/java/org/sonatype/mavenbook/weather中定义,它简单的调用???中定义的三个对象。在本章的样例中,我们正创建一个单独的项目,它包含了将会在web应用项目中被引用的service对象。这是一个在企业级Java开发中常见的模型,通常一个复杂的应用包含了不止一个的简单web应用。你可能拥有一个企业应用,它包含了多个web应用,以及一些命令行应用。通常你会想要 重构那些通用的逻辑至一个service类,以被很多项目重用。这就是我们创建WeatherService类的理由,在此之后,你就能看到simple-webapp项目是如何引用在simple-weather中定义的service对象。

retrieveForecast()方法接受一个包含邮政编码的String。之后这个邮政编码参数被传给YahooRetrieverretrieve()方法,后者从Yahoo! Weather获取XML。从YahooRetriever返回的XML被传给YahooParserparse()方法,后者继而又返回一个Weather对象。之后这个Weather对象被WeatherFormatter格式化成一个像样的String

                   6.4. simple-webapp 模块

simple-webapp模块是在simple-parent项目中引用的第二个子模块。这个web项目依赖于simple-weather模块,并且包含了一些用来展示从Yahoo! Weather服务查询到的结果的servlet

Example 6.4. simple-webapp 模块的 POM

<project xmlns="http://maven.apache.org/POM/4.0.0"

         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0

                      http://maven.apache.org/maven-v4_0_0.xsd">

  <modelVersion>4.0.0</modelVersion>

  <parent>

    <groupId>org.sonatype.mavenbook.ch06</groupId>

    <artifactId>simple-parent</artifactId>

    <version>1.0</version>

  </parent>

 

  <artifactId>simple-webapp</artifactId>

  <packaging>war</packaging>

  <name>simple-webapp Maven Webapp</name>

  <dependencies>

    <dependency>

      <groupId>org.apache.geronimo.specs</groupId>

      <artifactId>geronimo-servlet_2.4_spec</artifactId>

      <version>1.1.1</version>

    </dependency>

    <dependency>

      <groupId>org.sonatype.mavenbook.ch06</groupId>

      <artifactId>simple-weather</artifactId>

      <version>1.0</version>

    </dependency>

  </dependencies>

  <build>

    <finalName>simple-webapp</finalName>

    <plugins>

      <plugin>

        <groupId>org.mortbay.jetty</groupId>

        <artifactId>maven-jetty-plugin</artifactId>

      </plugin>

    </plugins>

  </build>

</project>

 

simple-weather模块定义了一个十分简单的servlet,它从HTTP请求读取一个邮政编码,调用Example 6.3, “WeatherService 类”中展示的WeatherService,然后将结果打印至HTTP响应的Writer

Example 6.5. simple-webapp WeatherServlet

package org.sonatype.mavenbook.web;

 

import org.sonatype.mavenbook.weather.WeatherService;

import java.io.*;

import javax.servlet.*;

import javax.servlet.http.*;

 

public class WeatherServlet extends HttpServlet {

    public void doGet(HttpServletRequest request,

                      HttpServletResponse response)

        throws ServletException, IOException {

        String zip = request.getParameter("zip" );

        WeatherService weatherService = new WeatherService();

        PrintWriter out = response.getWriter();

        try {

            out.println( weatherService.retrieveForecast( zip ) );

        } catch( Exception e ) {

            out.println( "Error Retrieving Forecast: " + e.getMessage() );

        }

        out.flush();

        out.close();

    }

}

 

WeatherServlet中,我们初始化了一个在simple-weather中定义的WeatherService类的实例。在请求参数中提供的邮政编码被传给retrieveForecast()方法,并且返回结果被打印至HTTP响应的Writer

最后,src/main/webapp/WEB-INF目录下的web.xml将所有这一切绑在一起。web.xml中的servletservlet-mapping元素将路径/weather匹配至WeatherServlet

Example 6.6. simple-webapp web.xml

<!DOCTYPE web-app PUBLIC

 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"

 "http://java.sun.com/dtd/web-app_2_3.dtd" >

 

<web-app>

  <display-name>Archetype Created Web Application</display-name>

  <servlet>

    <servlet-name>simple</servlet-name>

    <servlet-class>org.sonatype.mavenbook.web.SimpleServlet</servlet-class>

  </servlet>

  <servlet>

    <servlet-name>weather</servlet-name>

    <servlet-class>org.sonatype.mavenbook.web.WeatherServlet</servlet-class>

  </servlet>

  <servlet-mapping>

    <servlet-name>simple</servlet-name>

    <url-pattern>/simple</url-pattern>

  </servlet-mapping>

  <servlet-mapping>

    <servlet-name>weather</servlet-name>

    <url-pattern>/weather</url-pattern>

  </servlet-mapping>

</web-app>

 

                   6.5. 构建这个多模块项目

既然simple-weather项目包含了所有与Yahoo! Weather服务交互的代码,以及simple-webapp项目包含了一个简单的servlet,是时间将这个应用编译并打包成一个WAR文件了。为此,你会想要以合适的顺序编译并安装这两个项目;以为simple-webapp依赖于simple-weathersimple-weatherJAR需要在simple-webapp项目被编译之前就被创建好。为此,你需要从simple-parent项目运行mvn clean install命令。

~/examples/ch06/simple-parent$ mvn clean install

[INFO] Scanning for projects...

[INFO] Reactor build order:

[INFO]   Simple Parent Project

[INFO]   simple-weather

[INFO]   simple-webapp Maven Webapp

[INFO] ----------------------------------------------------------------------

[INFO] Building simple-weather

[INFO]    task-segment: [clean, install]

[INFO] ----------------------------------------------------------------------

[...]

[INFO] [install:install]

[INFO] Installing simple-weather-1.0.jar to simple-weather-1.0.jar

[INFO] ----------------------------------------------------------------------

[INFO] Building simple-webapp Maven Webapp

[INFO]    task-segment: [clean, install]

[INFO] ----------------------------------------------------------------------

[...]

[INFO] [install:install]

[INFO] Installing simple-webapp.war to simple-webapp-1.0.war

[INFO]

[INFO] ----------------------------------------------------------------------

[INFO] Reactor Summary:

[INFO] ----------------------------------------------------------------------

[INFO] Simple Parent Project ............................... SUCCESS [3.041s]

[INFO] simple-weather ...................................... SUCCESS [4.802s]

[INFO] simple-webapp Maven Webapp .......................... SUCCESS [3.065s]

[INFO] ----------------------------------------------------------------------

Maven执行一个带有子模块的项目的时候,Maven首先载入父POM,然后定位所有的子模块POMMaven然后将所有这些项目的POM放入到一个称为Maven 反应堆(Reactor)的东西中,由它负责分析模块之间的依赖关系。这个反应堆处理组件的排序,以确保相互独立的模块能以适当的顺序被编译和安装。

Note

除非需要做更改,反应堆一直维持定义在POM中的模块的顺序。为此一个有帮助的思维模型是,那些依赖于兄弟项目的项目在列表中被“向下按”,直到依赖顺序被满足。在很少的情况下,重新安排你构建的模块顺序可能很方便——例如你想要一个频繁的不稳定的模块接近构建的开端。

一旦反应堆解决了项目构建的顺序,Maven就会在多模块构建中为每个模块执行特定的目标。本例中,你能看到Mavensimple-webapp之前构建了simple-weather,为每个子模块执行了mvn clean install

Note