《Maven实战》读书笔记

依赖范围

compile

编译依赖范围。如果没有指定,就会默认使用该依赖范围。使用此依赖范围的Maven依赖,对于编译、测试、运行三种classpath都有效。典型的例子是spring-core,在编译、测试和运行的时候都需要使用该依赖。

test

测试依赖范围。使用此依赖范围的Maven依赖,只对于测试classpath有效,在编译主代码或者运行项目的使用时将无法使用此类依赖。典型的例子是JUnit,它只有在编译测试代码及运行测试的时候才需要。

provided

已提供依赖范围。使用此依赖范围的Maven依赖,对于编译和测试class-path有效,但在运行时无效。典型的例子是servlet-api,编译和测试项目的时候需要该依赖,但在运行项目的时候,由于容器已经提供,就不需要Maven重复地引入一遍。

runtime

运行时依赖范围。使用此依赖范围的Maven依赖,对于测试和运行class-path有效,但在编译主代码时无效。典型的例子是JDBC驱动实现,项目主代码的编译只需要JDK提供的JDBC接口,只有在执行测试或者运行项目的时候才需要实现上述接口的具体JDBC驱动。

system

系统依赖范围。该依赖与三种classpath的关系,和provided依赖范围完全一致。但是,使用system范围的依赖时必须通过systemPath元素显式地指定依赖文件的路径。由于此类依赖不是通过Maven仓库解析的,而且往往与本机系统绑定,可能造成构建的不可移植,因此应该谨慎使用。systemPath元素可以引用环境变量。

例如:

<dependency>
    <groupId>javax.sql</groupId>
    <artifactId>jdbc-stdext</artifactId>
    <version>2.0</version>
    <scope>system</scope>
    <systemPath>${java.home}/lib/rt.jar</systemPath>
</dependency>
import

导入依赖范围。该依赖范围不会对三种classpath产生实际的影响。

该范围的依赖只在dependencyManagement元素下才有效果,使用该范围的依赖通常指向一个POM,作用是将目标POM中的dependencyManagement配置导入并合并到当前POM的dependencyManagement元素中。例如想要在另外一个模块中使用与代码清单8-14完全一样的dependencyManagement配置,除了复制配置或者继承这两种方式之外,还可以使用import范围依赖将这一配置导入。

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>com.juvenxu.mvnbook.account</groupId>
            <artifactId>account-parent</artifactId>
            <version>1.0-SNAPSHOT</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

上述除import以外的各种依赖范围与三种classpath的关系如表所示:

依赖范围
(Scope)
对于编译classpath有效 对于测试classpath有效 对于运行classpath有效 例子
compile Y Y Y spring-core
test - Y - JUnit
provided Y Y - servlet-api
runtime - Y Y JDBC驱动实现
system Y Y - 本地的,Maven仓库之外的类库文件

如果不声明依赖范围,那么默认值就是compile,表示该依赖对主代码和测试代码都有效

传递性依赖

假设A依赖于B,B依赖于C,我们说A对于B是第一直接依赖,B对于C是第二直接依赖,A对于C是传递性依赖。第一直接依赖的范围和第二直接依赖的范围决定了传递性依赖的范围,如下表所示,最左边一列表示第一直接依赖范围,最上面一行表示第二直接依赖范围,中间的交叉单元格则表示传递性依赖范围。

compile test provided runtime
compile compile - - runtime
test test - - test
provided provided - provided provided
runtime runtime - runtime

这里再举个例子,以便更好地理解上表。account-email项目有一个com.icegreen:greenmail:1.3.1b的直接依赖,我们说这是第一直接依赖,其依赖范围是test;而greenmail又有一个javax.mail:mail:1.4的直接依赖,我们说这是第二直接依赖,其依赖范围是compile。显然javax.mail:mail:1.4是account-email的传递性依赖,对照上表可以知道,当第一直接依赖范围为test,第二直接依赖范围是compile的时候,传递性依赖的范围是test,因此javax.mail:mail:1.4是account-email的一个范围是test的传递性依赖。

规律:

  • 当第二直接依赖的范围是compile的时候,传递性依赖的范围与第一直接依赖的范围一致;
  • 当第二直接依赖的范围是test的时候,依赖不会得以传递;
  • 当第二直接依赖的范围是provided的时候,只传递第一直接依赖范围也为provided的依赖,且传递性依赖的范围同样为provided;
  • 当第二直接依赖的范围是runtime的时候,传递性依赖的范围与第一直接依赖的范围一致,但compile例外,此时传递性依赖的范围为runtime。

依赖调解

例如,项目A有这样的依赖关系:A->B->C->X(1.0)、A->D->X(2.0),X是A的传递性依赖,但是两条依赖路径上有两个版本的X,那么哪个X会被Maven解析使用呢?

Maven依赖调解(Dependency Mediation)的第一原则是:路径最近者优先。该例中X(1.0)的路径长度为3,而X(2.0)的路径长度为2,因此X(2.0)会被解析使用。

依赖调解第一原则不能解决所有问题,比如这样的依赖关系:A->B->Y(1.0)、A->C->Y(2.0),Y(1.0)和Y(2.0)的依赖路径长度是一样的,都为2。那么到底谁会被解析使用呢?

Maven定义了依赖调解的第二原则:第一声明者优先。在依赖路径长度相等的前提下,在POM中依赖声明的顺序决定了谁会被解析使用,顺序最靠前的那个依赖优胜。该例中,如果B的依赖声明在C之前,那么Y(1.0)就会被解析使用。

远程仓库的配置

  • 任何一个仓库声明的id必须是唯一的,尤其需要注意的是,Maven自带的中央仓库使用的id为central,如果其他的仓库声明也使用该id,就会覆盖中央仓库的配置。

  • 远程仓库配置示例(配置POM使用JBoss Maven仓库):

    <project>
        ……
        <repositories>
            <repository>
                <id>jboss</id>
                <name>JBoss Repository</name>
                <url>http://repository.jboss.com/maven2/</url>
                <releases>
                    <enabled>true</enabled>
                </releases>
                <snapshots>
                    <enabled>false</enabled>
                </snapshots>
                <layout>default</layout>
            </repository>
        </repositories>
        ……
    </project>
    
  • 对于releases和snapshots来说,除了enabled,它们还包含另外两个子元素updatePolicy和checksumPolicy:

    <snapshots>
        <enabled>true</enabled>
        <updatePolicy>daily</updatePolicy>
        <checksumPolicy>ignore</checksumPolicy>
    </snapshots>
    

    元素updatePolicy用来配置Maven从远程仓库检查更新的频率,默认的值是daily,表示Maven每天检查一次。其他可用的值包括:never—从不检查更新;always—每次构建都检查更新;interval:X—每隔X分钟检查一次更新(X为任意整数)。用户也可以使用命令行-U参数强制让Maven检查更新,如mvn clean install-U使用参数后,Maven就会忽略updatePolicy的配置

    元素checksumPolicy用来配置Maven检查检验和文件的策略。当构件被部署到Maven仓库中时,会同时部署对应的校验和文件。在下载构件的时候,Maven会验证校验和文件,如果校验和验证失败,怎么办?当checksumPolicy的值为默认的warn时,Maven会在执行构建时输出警告信息,其他可用的值包括:fail—Maven遇到校验和错误就让构建失败;ignore—使Maven完全忽略校验和错误。

  • 配置认证信息和配置仓库信息不同,仓库信息可以直接配置在POM文件中,但是认证信息必须配置在settings.xml文件中。

    <settings>
        ……
        <servers>
            <server>
                <id>my-proj</id>
                <username>repo-user</username>
                <password>repo-pwd</password>
            </server>
        </servers>
        ……
    </settings>
    
  • settings.xml中server元素的id必须与POM中需要认证的repository元素的id完全一致。换句话说,正是这个id将认证信息与仓库配置联系在了一起。

  • 在POM中配置构件部署地址,示例:

    <project>
        ……
        <distributionManagement>
            <repository>
                <id>proj-releases</id>
                <name>Proj Release Repository</name>
                <url>http://192.168.1.100/content/repositories/proj-releases</url>
            </repository>
            <snapshotRepository>
                <id>proj-snapshots</id>
                <name>Proj Snapshot Repository</name>
                <url>http://192.168.1.100/content/repositories/proj-snapshots</url>
            </snapshotRepository>
        </distributionManagement>
        ……
    </project>
    

镜像

如果仓库X可以提供仓库Y存储的所有内容,那么就可以认为X是Y的一个镜像。

配置中央仓库镜像(settings.xml)

<settings>
	……
    <mirrors>
        <mirror>
            <id>maven.net.cn</id>
            <name>one of the central mirrors in China</name>
            <url>http://maven.net.cn/content/groups/public/</url>
            <mirrorOf>central</mirrorOf>
        </mirror>
    </mirrors>
    ……
</settings>

该例中,mirrorOf的值为central,表示该配置为中央仓库的镜像,任何对于中央仓库的请求都会转至该镜像。

为了满足一些复杂的需求,Maven还支持更高级的镜像配置:

  • <mirrorOf>*</mirrorOf>:匹配所有远程仓库。

  • <mirrorOf>external:*</mirrorOf>:匹配所有远程仓库,使用localhost的除外,使用file://协议的除外。也就是说,匹配所有不在本机上的远程仓库。

  • <mirrorOf>repo1,repo2</mirrorOf>:匹配仓库repo1和repo2,使用逗号分隔多个远程仓库。

  • <mirrorOf>*,!repo1</mirrorOf>:匹配所有远程仓库,repo1除外,使用感叹号将仓库从匹配中排除。

需要注意的是,由于镜像仓库完全屏蔽了被镜像仓库,当镜像仓库不稳定或者停止服务的时候,Maven仍将无法访问被镜像仓库,因而将无法下载构件。

生命周期

Maven拥有三套相互独立的生命周期,它们分别为clean、default和site。clean生命周期的目的是清理项目,default生命周期的目的是构建项目,而site生命周期的目的是建立项目站点。

clean生命周期

clean生命周期的目的是清理项目,它包含三个阶段:

  1. pre-clean执行一些清理前需要完成的工作。
  2. clean清理上一次构建生成的文件。
  3. post-clean执行一些清理后需要完成的工作。
default生命周期

default生命周期定义了真正构建时所需要执行的所有步骤,它是所有生命周期中最核心的部分,其包含的阶段如下,这里只对重要的阶段进行解释:

validate

initialize

generate-sources

process-sources:处理项目主资源文件。一般来说,是对src/main/resources目录的内容进行变量替换等工作后,复制到项目输出的主classpath目录中。

generate-resources

process-resources

compile:编译项目的主源码。一般来说,是编译src/main/java目录下的Java文件至项目输出的主classpath目录中。

process-classes

generate-test-sources

process-test-sources:处理项目测试资源文件。一般来说,是对src/test/resources目录的内容进行变量替换等工作后,复制到项目输出的测试classpath目录中。

generate-test-resources

process-test-resources

test-compile:编译项目的测试代码。一般来说,是编译src/test/java目录下的Java文件至项目输出的测试classpath目录中。

process-test-classes

test:使用单元测试框架运行测试,测试代码不会被打包或部署。

prepare-package

package:接受编译好的代码,打包成可发布的格式,如JAR。

pre-integration-test

integration-test

post-integration-test

verify

install:将包安装到Maven本地仓库,供本地其他Maven项目使用。

deploy:将最终的包复制到远程仓库,供其他开发人员和Maven项目使用。

site生命周期

site生命周期的目的是建立和发布项目站点,Maven能够基于POM所包含的信息,自动生成一个友好的站点,方便团队交流和发布项目信息。该生命周期包含如下阶段:

pre-site执行一些在生成项目站点之前需要完成的工作。

site生成项目站点文档。post-site执行一些在生成项目站点之后需要完成的工作。

site-deploy将生成的项目站点发布到服务器上。

命令行与生命周期

mvn clean:该命令调用clean生命周期的clean阶段。实际执行的阶段为clean生命周期的pre-clean和clean阶段。

mvn test:该命令调用default生命周期的test阶段。实际执行的阶段为default生命周期的validate、initialize等,直到test的所有阶段。这也解释了为什么在执行测试的时候,项目的代码能够自动得以编译。

mvn clean install:该命令调用clean生命周期的clean阶段和default生命周期的install阶段。实际执行的阶段为clean生命周期的pre-clean、clean阶段,以及default生命周期的从validate至install的所有阶段。该命令结合了两个生命周期,在执行真正的项目构建之前清理项目是一个很好的实践。

mvn clean deploy site-deploy:该命令调用clean生命周期的clean阶段、default生命周期的deploy阶段,以及site生命周期的site-deploy阶段。实际执行的阶段为clean生命周期的pre-clean、clean阶段,default生命周期的所有阶段,以及site生命周期的所有阶段。该命令结合了Maven所有三个生命周期,且deploy为default生命周期的最后一个阶段,site-deploy为site生命周期的最后一个阶段。

自定义绑定插件目标

除了内置绑定以外,用户还能够自己选择将某个插件目标绑定到生命周期的某个阶段上,这种自定义绑定方式能让Maven项目在构建过程中执行更多更富特色的任务。

一个常见的例子是创建项目的源码jar包,内置的插件绑定关系中并没有涉及这一任务,因此需要用户自行配置。maven-source-plugin可以帮助我们完成该任务,它的jar-no-fork目标能够将项目的主代码打包成jar文件,可以将其绑定到default生命周期的verify阶段上,在执行完集成测试后和安装构件之前创建源码jar包。

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-source-plugin</artifactId>
            <version>2.1.1</version>
            <executions>
                <execution>
                    <id>attach-sources</id>
                    <phase>verify</phase>
                    <goals>
                        <goal>jar-no-fork</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

executions下每个execution子元素可以用来配置执行一个任务。该例中配置了一个id为attach-sources的任务,通过phrase配置,将其绑定到verify生命周期阶段上,再通过goals配置指定要执行的插件目标。至此,自定义插件绑定完成。运行mvn verify就能看到如下输出:

[INFO]——maven-source-plugin:2.1.1:jar-no-fork(attach-sources)@my-proj——
[INFO]Building jar:D:\code\ch-7\target\my-proj-0.0.1-SNAPSHOT-sources.jar

我们可以看到,当执行verify生命周期阶段的时候,maven-source-plugin:jar-no-fork会得以执行,它会创建一个以-sources.jar结尾的源码文件包。

有时候,即使不通过phase元素配置生命周期阶段,插件目标也能够绑定到生命周期中去。例如,可以尝试删除上述配置中的phase一行,再次执行mvn verify,仍然可以看到maven-source-plugin:jar-no-fork得以执行。出现这种现象的原因是:有很多插件的目标在编写时已经定义了默认绑定阶段。可以使用maven-help-plugin查看插件详细信息,了解插件目标的默认绑定阶段。运行命令如下:

$mvn help:describe-Dplugin=org.apache.maven.plugins:maven-source-plugin:2.1.1-Ddetail

该命令输出对应插件的详细信息。在输出信息中,能够看到关于目标jar-no-fork的如下信息:

……
source:jar-no-fork
Description:This goal bundles all the sources into a jar archive.This goal functions the same as the jar goal but does not fork the build and is suitable for attaching to the build lifecycle. Deprecated.No reason given Implementation:org.apache.maven.plugin.source.SourceJarNoForkMojo 
Language:java 
Bound to phase:package 
Available parameters:
……

该输出包含了一段关于jar-no-fork目标的描述,这里关心的是Bound to phase这一项,它表示该目标默认绑定的生命周期阶段(这里是package)。也就是说,当用户配置使用maven-source-plugin的jar-no-fork目标的时候,如果不指定phase参数,该目标就会被绑定到package阶段。

命令行插件配置

我们会经常从命令行输入并执行Maven命令。在这种情况下,如果能够方便地更改某些插件的行为,无疑会十分方便。很多插件目标的参数都支持从命令行配置,用户可以在Maven命令中使用-D参数,并伴随一个参数键=参数值的形式,来配置插件目标的参数。

例如,maven-surefire-plugin提供了一个maven.test.skip参数,当其值为true的时候,就会跳过执行测试。于是,在运行命令的时候,加上如下-D参数就能跳过测试:

mvn install-Dmaven.test.skip=true

参数-D是Java自带的,其功能是通过命令行设置一个Java系统属性,Maven简单地重用了该参数,在准备插件的时候检查系统属性,便实现了插件参数的配置。

POM中插件全局配置

并不是所有的插件参数都适合从命令行配置,有些参数的值从项目创建到项目发布都不会改变,或者说很少改变,对于这种情况,在POM文件中一次性配置就显然比重复在命令行输入要方便。

用户可以在声明插件的时候,对此插件进行一个全局的配置。也就是说,所有该基于该插件目标的任务,都会使用这些配置。

用户可以在声明插件的时候,对此插件进行一个全局的配置。也就是说,所有该基于该插件目标的任务,都会使用这些配置。例如,我们通常会需要配置maven-compiler-plugin告诉它编译Java 1.5版本的源文件,生成与JVM 1.5兼容的字节码文件。

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>2.1</version>
            <configuration>
                <source>1.5</source>
                <target>1.5</target>
            </configuration>
        </plugin>
    </plugins>
</build>

这样,不管绑定到compile阶段的maven-compiler-plugin:compile任务,还是绑定到test-compiler阶段的maven-compiler-plugin:testCompiler任务,就都能够使用该配置,基于Java 1.5版本进行编译。

POM中插件任务配置

除了为插件配置全局的参数,用户还可以为某个插件任务配置特定的参数。以maven-antrun-plugin为例,它有一个目标run,可以用来在Maven中调用Ant任务。用户将maven-antrun-plugin:run绑定到多个生命周期阶段上,再加以不同的配置,就可以让Maven在不同的生命阶段执行不同的任务。

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-antrun-plugin</artifactId>
            <version>1.3</version>
            <executions>
                <execution>
                    <id>ant-validate</id>
                    <phase>validate</phase>
                    <goals>
                        <goal>run</goal>
                    </goals>
                    <configuration>
                        <tasks>
                            <echo>I'm bound to validate phase.</echo>
                        </tasks>
                    </configuration>
                </execution>
                <execution>
                    <id>ant-verify</id>
                    <phase>verify</phase>
                    <goals>
                        <goal>run</goal>
                    </goals>
                    <configuration>
                        <tasks>
                            <echo>I'm bound to verify phase.</echo>
                        </tasks>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

插件文档

可以去Maven – Available Plugins (apache.org)这个网站查找插件的文档。

除了访问在线的插件文档之外,还可以借助maven-help-plugin来获取插件的详细信息。可以运行如下命令来获取maven-compiler-plugin 2.1版本的信息:

$mvn help:describe-Dplugin=org.apache.maven.plugins:maven-compiler-plugin:2.1

在描述插件的时候,还可以省去版本信息,让Maven自动获取最新版本来进行表述。

$mvn help:describe-Dplugin=org.apache.maven.plugins:maven-compiler-plugin

进一步简化,可以使用插件目标前缀替换坐标。例如:

$mvn help:describe-Dplugin=compiler

如果想仅仅描述某个插件目标的信息,可以加上goal参数:

$mvn help:describe-Dplugin=compiler-Dgoal=compile

如果想让maven-help-plugin输出更详细的信息,可以加上detail参数:

$mvn help:describe-Dplugin=compiler-Ddetail

聚合

account-aggregator的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.0http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.juvenxu.mvnbook.account</groupId>
    <artifactId>account-aggregator</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>pom</packaging>
    <name>Account Aggregator</name>
    <modules>
        <module>account-email</module>
        <module>account-persist</module>
    </modules>
</project>

对于聚合模块来说,其打包方式packaging的值必须为pom,否则就无法构建。

继承

account-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.0http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.juvenxu.mvnbook.account</groupId>
    <artifactId>account-parent</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>pom</packaging>
    <name>Account Parent</name>
</project>

account-email的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.0http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>com.juvenxu.mvnbook.account</groupId>
        <artifactId>account-parent</artifactId>
        <version>1.0.0-SNAPSHOT</version>
        <relativePath>../account-parent/pom.xml</relativePath>
    </parent>
    <artifactId>account-email</artifactId>
    <name>Account Email</name>
    <dependencies>……</dependencies>
    <build>
        <plugins>……</plugins>
    </build>
</project>

POM中没有为account-email声明groupId和version,不过这并不代表account-email没有groupId和version。实际上,这个子模块隐式地从父模块继承了这两个元素,这也就消除了一些不必要的配置。

groupId和version是可以被继承的,那么还有哪些POM元素可以被继承呢?以下是一个完整的列表,并附带了简单的说明:

groupId:项目组ID,项目坐标的核心元素。

version:项目版本,项目坐标的核心元素。

description:项目的描述信息。

organization:项目的组织信息。

inceptionYear:项目的创始年份。

url:项目的URL地址。

developers:项目的开发者信息。

contributors:项目的贡献者信息。

distributionManagement:项目的部署配置。

issueManagement:项目的缺陷跟踪系统信息。

ciManagement:项目的持续集成系统信息。

scm:项目的版本控制系统信息。

mailingLists:项目的邮件列表信息。

properties:自定义的Maven属性。

dependencies:项目的依赖配置。

dependencyManagement:项目的依赖管理配置。

repositories:项目的仓库配置。

build:包括项目的源码目录配置、输出目录配置、插件配置、插件管理配置等。

reporting:包括项目的报告输出目录配置、报告插件配置等。

依赖管理

Maven提供的dependencyManagement元素既能让子模块继承到父模块的依赖配置,又能保证子模块依赖使用的灵活性。在dependencyManagement元素下的依赖声明不会引入实际的依赖,不过它能够约束dependencies下的依赖使用。

例如,可以在account-parent中加入这样的dependencyManagement配置:

<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.0http://maven.apache.org/maven-v4_0_0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.juvenxu.mvnbook.account</groupId>
    <artifactId>account-parent</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <packaging>pom</packaging>
    <name>Account Parent</name>
    <properties>
        <springframework.version>2.5.6</springframework.version>
        <junit.version>4.7</junit.version>
    </properties>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-core</artifactId>
                <version>${springframework.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-beans</artifactId>
                <version>${springframework.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>${springframework.version}</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context-support</artifactId>
                <version>${springframework.version}</version>
            </dependency>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>${junit.version}</version>
                <scope>test</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

这里使用dependencyManagement声明的依赖既不会给account-parent引入依赖,也不会给它的子模块引入依赖,不过这段配置是会被继承的。现在修改account-email的POM如下:

<properties>
    <javax.mail.version>1.4.1</javax.mail.version>
    <greenmail.version>1.3.1b</greenmail.version>
</properties>
<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-beans</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context-support</artifactId>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
    </dependency>
    <dependency>
        <groupId>javax.mail</groupId>
        <artifactId>mail</artifactId>
        <version>${javax.mail.version}</version>
    </dependency>
    <dependency>
        <groupId>com.icegreen</groupId>
        <artifactId>greenmail</artifactId>
        <version>${greenmail.version}</version>
        <scope>test</scope>
    </dependency>
</dependencies>

上述POM中的依赖配置较原来简单了一些,所有的springframework依赖只配置了groupId和artifactId,省去了version,而junit依赖不仅省去了version,还省去了依赖范围scope。这些信息可以省略是因为account-email继承了account-parent中的dependencyManage-ment配置,完整的依赖声明已经包含在父POM中,子模块只需要配置简单的groupId和artifactId就能获得对应的依赖信息,从而引入正确的依赖。

插件管理

在父POM中配置pluginManagement

<build>
    <pluginManagement>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-source-plugin</artifactId>
                <version>2.1.1</version>
                <executions>
                    <execution>
                        <id>attach-sources</id>
                        <phase>verify</phase>
                        <goals>
                            <goal>jar-no-fork</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </pluginManagement>
</build>

继承了pluginManagement后的插件配置

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-source-plugin</artifactId>
        </plugin>
    </plugins>
</build>

超级POM

任何一个Maven项目都隐式地继承自该POM,大量超级POM的配置都会被所有Maven项目继承,这些配置也就成为了Maven所提倡的约定。对于Maven 3,超级POM在文件$MAVEN_HOME/lib/maven-model-builder-x.x.x.jar中的org/apache/maven/model/pom-4.0.0.xml路径下。

裁剪反应堆

一般来说,用户会选择构建整个项目或者选择构建单个模块,但有些时候,用户会想要仅仅构建完整反应堆中的某些个模块。换句话说,用户需要实时地裁剪反应堆。

Maven提供很多的命令行选项支持裁剪反应堆,输入mvn-h可以看到这些选项:

-am,——also-make同时构建所列模块的依赖模块

-amd,–also-make-dependents同时构建依赖于所列模块的模块

-pl,——projects<arg>构建指定的模块,模块间用逗号分隔

-rf,–resume-from<arg>从指定的模块回复反应堆

例子:

可以使用-pl选项指定构建某几个模块,如运行如下命令:

$mvn clean install-pl account-email,account-persist

使用-am选项可以同时构建所列模块的依赖模块。例如:

$mvn clean install-pl account-email-am

使用-amd选项可以同时构建依赖于所列模块的模块。例如:

$ mvn clean install -pl account-parent -amd

使用-rf选项可以在完整的反应堆构建顺序基础上指定从哪个模块开始构建。例如:

mvn clean install -rf account-email

最后,在-pl -am或者-pl -amd的基础上,还能应用-rf参数,以对裁剪后的反应堆再次裁剪。例如:

mvn clean install -pl account-parent -amd -rf account-email

该命令中的-pl和-amd参数会裁剪出一个account-parent、account-email和account-persist的反应堆,在此基础上,-rf参数指定从account-email参数构建。因此会得到如下的反应堆:

[INFO] -------------------------------------------------------------
[INFO] Reactor Build Order:
[INFO]
[INFO] Account Email
[INFO] Account Persist
[INFO]
[INFO] -------------------------------------------------------------

在开发过程中,灵活应用上述4个参数,可以帮助我们跳过无须构建的模块,从而加速构建。在项目庞大、模块特别多的时候,这种效果就会异常明显。

动态指定要运行的测试用例

maven-surefire-plugin提供了一个test参数让Maven用户能够在命令行指定要运行的测试用例。例如,如果只想运行account-captcha的RandomGeneratorTest,就可以使用如下命令:

$mvn test-Dtest=RandomGeneratorTest

这里test参数的值是测试用例的类名,这行命令的效果就是只有RandomGeneratorTest这一个测试类得到运行。

maven-surefire-plugin的test参数还支持高级一些的赋值方式,能让用户更灵活地指定需要运行的测试用例。例如:

$mvn test-Dtest=Random*Test

星号可以匹配零个或多个字符,上述命令会运行项目中所有类名以Random开头、Test结尾的测试类。

除了星号匹配,还可以使用逗号指定多个测试用例:

$mvn test-Dtest=RandomGeneratorTest,AccountCaptchaServiceTest

该命令的test参数值是两个测试类名,它们之间用逗号隔开,其效果就是告诉Maven只运行这两个测试类。

当然,也可以结合使用星号和逗号。例如:

$mvn test-Dtest=Random*Test,AccountCaptchaServiceTest

需要注意的是,上述几种从命令行动态指定测试类的方法都应该只是临时使用,如果长时间只运行项目的某几个测试,那么测试就会慢慢失去其本来的意义。

test参数的值必须匹配一个或者多个测试类,如果maven-surefire-plugin找不到任何匹配的测试类,就会报错并导致构建失败。可以加上-DfailIfNoTests=false,告诉maven-surefire-plugin即使没有任何测试也不要报错:

$mvn test-Dtest-DfailIfNoTests=false

包含与排除测试用例

我们看到,使用test参数用户可以从命令行灵活地指定要运行的测试类。可惜的是,maven-surefire-plugin并没有提供任何参数支持用户从命令行跳过指定的测试类,好在用户可以通过在POM中配置maven-surefire-plugin排除特定的测试类。

例如,由于历史原因,有些项目所有测试类名称都以Tests结尾,这样的名字不符合默认的3种模式,因此不会被自动运行,用户可以通过代码清单10-14所示的配置让Maven自动运行这些测试。

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.5</version>
    <configuration>
        <includes>
            <include>**/*Tests.java</include>
        </includes>
    </configuration>
</plugin>

类似地,也可以使用excludes元素排除一些符合默认命名模式的测试类:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.5</version>
    <configuration>
        <excludes>
            <exclude>**/*ServiceTest.java</exclude>
            <exclude>**/TempDaoTest.java</exclude>
        </excludes>
    </configuration>
</plugin>

上述代码清单排除了所有以ServiceTest结尾的测试类,以及一个名为TempDaoTest的测试类。它们都符合默认的命名模式**/*Test.java,不过,有了excludes配置后,maven-surefire-plugin将不再自动运行它们。


《Maven实战》读书笔记
https://www.zhaojun.inkhttps://www.zhaojun.ink/archives/maven-read-notes
作者
卑微幻想家
发布于
2022-11-24
许可协议