目录
Maven[1]
1.基础知识
1.1.Maven 相关目录、文件
Maven 安装路径下,各目录、文件解析:
-
bin/
:包含了mvn
运行的脚本,这些脚本用来配置 Java 命令,准备好 classpath 和相关的 Java 系统属性,然后执行 Java 命令。mvn
指令其后可添加插件目标或者生命周期阶段。mvnDebug
比mvn
多了一条MAVEN_DEBUG_OPTS
配置,其作用是在运行 Maven 时开启 Debug,以便调试 Maven 本身。m2.conf
是 classworlds 的配置文件。 -
boot/
:仅包含 plexus-classworlds-2.2.3.jar。plexus-classworlds 是一个类加载器框架,相对于默认的 java 类加载器,它提供了更丰富的语法以方便配置,Maven 使用该框架加载自己的类库。
-
conf/
:包含 settings.xml,用于全局地定制 Maven 的行为。推荐将该文件复制一份到~/.m2/
目录下,用于用户范围地定制 Maven 行为,且在 Maven 更新安装时保存自己的设置。 -
lib/
:包含所有 Maven 运行时需要的 Java 类库,Maven 本身是分模块开发的。此外,这里还包含一些 Maven 用到的第三方依赖。(对于Maven2,只包含maven-2.2.1-uber.jar 的文件,原本各为独立 JAR 文件的 Maven 模块和第三方类库都被拆解后重新合并到了这个 JAR 文件中)超级 POM 是 maven-model-builder-x.x.x.jar 的 org/apache/maven/model/pom-4.0.0.xml,该 POM 配置了远程依赖仓库、远程插件仓库、项目目录配置等。所有 POM 均继承超级 POM。超级 POM 有相关设置,如中央依赖仓库、中央插件仓库、各约定目录等。
-
LICENSE.txt
记录了 Maven 使用的软件许可证 Apache License Version 2.0;NOTICE.txt
记录了 Maven 包含的第三方软件;而README.txt
则包含了 Maven 的简要介绍,包括安装需求及如何安装的简要指令等。
1.1.1.setting.xml 文件设置
HTTP 代理:
<settings>
...
<proxies>
<proxy> <!-- 配置多条时只有第一个被激活的生效 -->
<id>optional</id> <!-- 声明 id -->
<active>true</active> <!-- 是否激活 -->
<protocol>http</protocol> <!-- 代理协议 -->
<username>proxyuser</username> <!-- 需要认证时的用户名 -->
<password>proxypass</password> <!-- 需要认证时的密码 -->
<host>proxy.host.net</host> <!-- 主机名 -->
<port>80</port> <!-- 端口 -->
<nonProxyHosts>local.net|some.host.com</nonProxyHosts> <!-- 哪些主机名不需要代理 -->
</proxy>
</proxies>
...
</settings>
1.2.POM 示例
<?xml version = "1.0" encoding = "UTF-8"?>
<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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>xyz.meyok.maven</groupId>
<artifactId>hello-world</artifactId>
<version>1.0-SNAPSHOT</version>
<name>Maven Hello World Project</name>
</project>
name 声明了一个对于用户更为友好的项目名称,虽然这不是必须的,但还是推荐为每个 POM 声明 name,以方便信息交流。
jar 包名字也可以用finalName
重新定义。
1.3.基础设置
运行mvn
命令实际上是执行了Java
命令,而Java
命令参数可通过MAVEN_OPTS
设置。通常需要设置MAVEN_OPTS
的值为-Xms128m-Xmx512m
,因为 Java 默认的最大可用内存往往不能够满足 Maven 运行的需要。
2.坐标和依赖
2.1.坐标
依赖的坐标元素有:groupId、artifactId、version、packaging、classifier。groupId、artifactId、version 是必须定义的,packaging 是可选的,而 classifier 是不能直接定义的。
-
groupId:表示实际项目 id,通常与域名(并非项目隶属的组织或公司)反向一一对应。Maven 项目和实际项目不一定是一对一的关系,比如 SpringFramework 这一实际项目,其对应的 Maven 项目会有很多,如 spring-core、spring-context 等,这是由于 Maven 中模块的概念,因此,一个实际项目往往会被划分成很多模块。
-
artifactId:Maven 项目 id。推荐该 id 含有实际项目名称前缀,如 SpringFramework 实际项目的 spring-core Maven 项目含有前缀 “spring”。
-
version:Maven 项目当前所处的版本。
-
packaging:Maven 项目的打包方式。打包方式会影响到构建的生命周期。
默认值为 jar,常见的打包类型还有 war、pom、maven-plugin、ear 等。
packaging 并非一定与构件扩展名对应,比如 packaging 为 maven-plugin 的构件扩展名为 jar。
-
classifier:用来帮助定义构建输出的一些附属构件。附属构件与主构件对应,如主构件是 nexus-indexer-2.0.0.jar,该项目可能还会通过使用一些插件生成如 nexus-indexer-2.0.0-javadoc.jar、nexus-indexer-2.0.0-sources.jar 这样一些附属构件,其包含了 Java 文档和源代码。这时候,javadoc 和 sources 就是这两个附属构件的 classifier。这样,附属构件也就拥有了自己唯一的坐标。还有一个关于 classifier 的典型例子是 TestNG,TestNG 的主构件是基于 Java 1.4 平台的,而它又提供了一个 classifier 为 jdk5 的附属构件。注意,不能直接定义项目的 classifier,因为附属构件不是项目直接默认生成的,而是由附加的插件帮助生成。
2.2.依赖
<dependency>
<groupId></groupId>
<artifactId></artifactId>
<version></version>
<type></type>
<scope></scope>
<optional></optional>
<exclusions>
<exclusion></exclusion>
</exclusions>
</dependency>
2.2.1.依赖范围
Maven 在编译主代码和测试代码、运行测试代码、运行主代码等时期,会使用不同的 classpath,设置依赖范围确定是否将对应的构件添加到对应时期的 classpath 中。
scope 元素设置依赖范围(默认为 compile),可设置的值有:
-
compile:编译、测试、运行。
-
test:测试依赖范围。只对于测试 classpath。编译主代码、运行项目都无法使用此依赖。
-
provided:已提供依赖范围。编译测试 classpath,运行时无效。如 servlet-api,因为大部分 servlet 容器含有该依赖,若此时再添加可能会引起冲突。
-
runtime:运行范围依赖。测试、运行 classpath 有效,编译主代码时无效。如 JDBC 驱动。
-
system:系统依赖范围。与 provided 范围完全一致,但此依赖不是 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>
此外,在设置依赖管理时(dependencyManagement 元素),依赖范围可被设置为 import(Maven2.0.9 及以上),表示导入目标依赖的 dependencyManagement 元素到当前依赖管理。
2.2.2.传递性依赖
Maven 会解析各个直接依赖的 POM,将那些必要的间接依赖,以传递性依赖的形式引入到当前的项目中。
传递性依赖的依赖范围如下(第一列为直接依赖的依赖范围,第一行为直接依赖的依赖设置的依赖范围):
出现重复(指 groupId、artifactId 相同)的传递性依赖时,只会传递选择一个依赖,其选择策略是:路径(指被传递几次)最近者优先,路径相同时第一生命者优先。
optional 元素(默认 false)可以设置对应依赖是否被传递。如 B 设置 X、Y 依赖时设置了 optional 为 true,即可选依赖,那么 X、Y 就不会被传递给依赖 B 的构件 A:
设置依赖时,可以添加 exclusions 元素来排除那些传递性依赖。exclusion 只需要设置 groupId、artifactId。
2.2.3.优化依赖
-
版本设置可以通过 properties 元素进行统一设置,如:
<properties> <springframework.version>2.5.6</springframework.version> </properties> </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> </dependencies>
-
可执行以下插件目标来查看当前项目的依赖,根据结果添加、删除相应的依赖:
mvn dependency:list # 获取以解析依赖 mvn dependency:tree # 获取依赖树 mvn dependency:analyze # 分析编译主代码、测试代码的依赖(测试、运行时需要的依赖分析不了),显示缺少的依赖和未使用的依赖。
3.生命周期和插件
3.1.生命周期
Maven 拥有三套相互独立的生命周期,每个生命周期包含一些后者依赖前者的阶段。
-
clean:清理项目。其阶段包含:pre-clean、clean、post-clean。
阶段 任务 pre-clean 执行一些清理前需要完成的工作 clean 清理上一次构建生成的文件 post-clean 执行一些清理后需要完成的工作 -
default:构建项目。其阶段包含:validate、initialize、generate-sources、process-sources、generate-resources、process-resources、compile、process-classes、generate-test-sources、process-test-sources、test-compile、test、prepare-package、package、pre-integration-test、integration-test、post-integration-test、verify、install、deploy。
阶段 任务 validate 验证项目的正确性,例如检查项目的版本是否正确。 process-sources 处理项目主资源文件。一般来说,是对 src/main/resources 目录的内容进行变量替换等工作后,复制到项目输出的主 classpath 目录中。 compile 编译项目的主源码。一般来说,是编译 src/main/java 目录下的 Java 文件至项目输出的主 classpath 目录中。 process-test-sources 一般来说,是对 src/test/resources 目录的内容进行变量替换等工作后,复制到项目输出的测试 classpath 目录中。 test-compile 编译项目的测试代码。一般来说,是编译 src/test/java 目录下的 Java 文件至项目输出的测试 classpath 目录中。 test 使用单元测试框架运行测试,测试代码不会被打包或部署。 package 接受编译好的代码,打包成可发布的格式,如 JAR。 verify 对项目进行额外的检查以确保质量。 install 将包安装到 Maven 本地仓库,供本地其他 Maven 项目使用。 deploy 将最终的包复制到远程仓库,供其他开发人员和 Maven 项目使用。 -
site:建立和发布项目站点。Maven 能够基于 POM 所包含的信息,自动生成一个友好的站点,方便团队交流和发布项目信息。其阶段包含: pre-site、site、post-site、site-deploy。
阶段 任务 pre-site 执行一些在生成项目站点之前需要完成的工作 site 生成项目站点文档 post-site 执行一些在生成项目站点之后需要完成的工作 site-deploy 将生成的项目站点发布到服务器上
3.2.插件
Maven 的生命周期是抽象的,这意味着生命周期本身不做任何实际的工作。在 Maven 的设计中,实际的任务(如编译源代码)都交由插件目标来完成。注意插件目标不仅仅是用来实现生命周期阶段的。
3.2.1.绑定生命周期阶段
Maven 为大多数构建步骤编写并绑定了默认插件目标(未列出的生命周期阶段则未内置绑定):
-
clean:
生命周期阶段 插件目标 clean maven-clean-plugin:clean -
default:
生命周期阶段 插件目标 process-resources maven-resources-plugin:resources compile maven-compiler-plugin:compile process-test-resources maven-resources-plugin:testResources test-compile maven-compiler-plugin:testCompile test maven-surefire-plugin:test package maven-jar-plugin:jar install maven-install-plugin:install deploy maven-deploy-plugin:deploy -
site:
生命周期阶段 插件目标 site maven-site-plugin:site site-deploy maven-site-plugin:deploy
有很多插件的目标在编写时已经定义了默认绑定阶段(注意与上述区别,上述指生命周期默认绑定的插件,这里指插件默认绑定的生命周期),可通过 help:describe 插件目标查看,如:
mvn help:describe -DgroupId=org.apache.maven.plugins -DartifactId=maven-source-plugin -Ddetail=true
在 POM 中,可以自定义插件的绑定周期,如创建项目的源码 jar 包(Maven 并为其绑定默认的插件)可通过以下插件进行绑定(省略 phase 时会使用插件默认的绑定周期,这里是 verify,所以以下示例中 phase 可以省略):
<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>
3.2.2.插件设置
对插件全局设置,可在 POM 中对应插件的 configuration 下进行配置。如 Maven 的核心插件之一 compiler 插件默认只支持编译 Java 1.3。如果未在 properties 元素中设置 jdk 版本,需要进行如下插件设置:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<!--
| jdk8 及以前
<source>1.x</source>
<target>1.x</target>
-->
<release>11</release>
</configuration>
</plugin>
对插件任务配置,可在 POM 中对应插件任务的 configuration 下进行配置。如要让 jar 包包含主类信息,可进行如下插件设置(package 时,会生成 jar 和带 original 前缀的 jar,前者带主类信息后者不带,install 时只有前者被安装。):
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>1.2.1</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation = "org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>xyz.meyok.maven.helloworld.HelloWorld</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
有的插件目标参数提供了表达式(并不是所有,有的只能在 POM 文件中配置),可以通过命令行设置该插件目标参数。如 maven-surefire-plugin 提供 maven.test.skip 参数,为 true 时跳过执行测试:
mvn install -Dmaven.test.skip=true
4.仓库
Maven 仓库分类:
本地仓库并不区分依赖仓库、插件仓库,但远程仓库需要区分。
4.1.本地仓库
默认的本地仓库是~/.m2/repository
。可以在 setting.xml 文件中修改本地仓库:
<localRepository>/path/to/local/repo</localRepository>
4.2.远程仓库
在 POM 下,可通过 repository 元素配置使用的远程依赖仓库、通过 pluginRepository 元素配置使用的远程插件仓库,如配置 POM 使用 JBoss Maven 远程依赖仓库:
<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>
任何一个仓库声明中 id 必须唯一(中央仓库 id 为 central,如果其它仓库声明也是 central 则会覆盖中央仓库配置)。
releases、snapshot 元素中:
- enabled:设置了是否对该仓库开启发布版本、快照版本的下载支持。
- updatePolicy:检查更新频率。默认 daily 每天,还有 never 从不、always 每次构建、interval:X 每隔 X 分钟。
- checksumPolicy:检验策略。默认 warn 检查失败发出警告信息,还有 fail 检查失败构建失败、ignore 忽略。
若要将项目生成的构建部署到远程仓库中,在 POM 文件中需要设置 distributionManagement 元素。如:
<distributionManagement>
<repository>
<id>nexus-releases</id>
<name>Proj Releases Repository</name>
<url>http://192.168.1.100/content/repository/proj-releases</url>
</repository>
<snapshotRepository>
<id>nexus-snapshots</id>
<name>Proj Snapshots Repository</name>
<url>http://192.168.1.100/content/repository/proj-snapshots</url>
</snapshotRepository>
</distributionManagement>
id 为远程仓库唯一标识,url 指出远程仓库地址。配置正确后,执行 deploy 阶段时,会根据是发布版本还是快照版本部署到对应的远程仓库。
如果远程仓库需要认证,需要在 setting.xml 文件中设置远程仓库认证(注意该 id 和上述 id 应该相同):
<servers>
<server>
<id>nexus-releases</id>
<username>repo-user</username>
<password>repo-pwd</password>
</server>
</servers>
4.2.1.中央仓库
默认的中央依赖仓库和中央插件仓库已在超级 POM 中设置:
<repositories>
<repository>
<id>central</id>
<name>Central Repository</name>
<url>https://repo.maven.apache.org/maven2</url>
<layout>default</layout>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>central</id>
<name>Central Repository</name>
<url>https://repo.maven.apache.org/maven2</url>
<layout>default</layout>
<snapshots>
<enabled>false</enabled>
</snapshots>
<releases>
<updatePolicy>never</updatePolicy>
</releases>
</pluginRepository>
</pluginRepositories>
4.2.2.私服
有三种专门的 Maven 仓库管理软件可以用来帮助大家建立私服:Apache 基金会的 Archiva、JFrog 的 Artifactory 和 Sonatype 的 Nexus。
Nuxes 私服
Nexus 是典型的 Java Web 应用,它有两种安装包,一种是包含 Jetty 容器的 Bundle 包,另一种是不包含 Web 容器的 war 包。以 Bundle 包安装后安装目录解析:
nexus-webapp-1.7.2/
:该目录包含了Nexus运行所需要的文件,如启动脚本、依赖jar包等。sonatype-work/
:该目录包含Nexus生成的配置文件、日志文件、仓库文件等。
第一个目录是运行 Nexus 所必需的,而且所有相同版本 Nexus 实例所包含的该目录内容都是一样的。而第二个目录不是必须的,Nexus 会在运行的时候动态创建该目录,不过它的内容对于各个 Nexus 实例是不一样的,因为不同用户在不同机器上使用的 Nexus 会有不同的配置和仓库内容。当用户需要备份 Nexus 的时。候,默认备份sonatype-work/
目录,因为该目录包含了用户特定的内容,而 nexus-webapp-1.7.2 目录下的内容是可以从安装包直接获得的。
conf/plexus.properties/application-port
中有 Nexus 的端口设置,默认为 8081。
Nexus包含了各种类型的仓库概念,包括代理仓库、宿主仓库和仓库组等。每一种仓库都提供了丰富实用的配置参数,方便用户根据需要进行定制。仓库有四种类型:group(仓库组)、hosted(宿主)、proxy(代理)和 virtual(虚拟)。每个仓库的格式为 maven2 或者 maven1。此外,仓库还有一个属性为 Policy(策略),表示该仓库为发布(Release)版本仓库还是快照(Snapshot)版本仓库。最后两列的值为仓库的状态和路径。
Maven 可以直接从宿主仓库下载构件;Maven 也可以从代理仓库下载构件,而代理仓库会间接地从远程仓库下载并缓存构件;最后,为了方便,Maven 可以从仓库组下载构件,而仓库组没有实际内容(图中用虚线表示),它会转向其包含的宿主仓库或者代理仓库获得实际构件的内容。
Maven 提供 Profile 机制,能让用户将仓库配置放到 setting.xml 中的 profile 中。如:
<profiles>
<profile>
<id>nexus</id>
<repositories>
<repository>
<id>nexus</id>
<name>Nexus</name>
<url>私服依赖仓库地址</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>nexus</id>
<name>Nexus</name>
<url>私服插件仓库地址</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</profile>
<profiles>
<activeProfiles>
<activeProfile>nexus</activeProfile>
</activeProfiles>
4.3.镜像
如果远程仓库 X 可以提供远程仓库 Y 存储的所有内容,那么就可以认为 X 是 Y 的一个镜像仓库。通过 setting.xml 中 mirror 元素可以配置镜像仓库。如配置私服作为所有远程仓库的镜像:
<mirrors>
<mirror>
<id>internal-repository</id>
<mirrorOf>*</mirrorOf>
<name>Internal Repository Manager</name>
<url>http://192.168.1.100/maven2/</url>
</mirror>
</mirrors>
mirrorOf 取值解释:
repo1, repo2
:匹配仓库 repo1 和 repo2,使用逗号分隔多个远程仓库。如匹配中央仓库 central。*
:匹配所有远程仓库。*, !repo1
:匹配所有远程仓库,repo1 除外。external:*
:匹配所有远程仓库,使用 localhost 的除外、file:// 协议的除外。也就是说,匹配所有不在本机上的远程仓库。
需要注意的是,由于镜像仓库完全屏蔽了被镜像仓库,当镜像仓库不稳定或者停止服务的时候,Maven 仍将无法访问被镜像仓库,因而将无法下载构件。
4.4.版本解析
4.4.1.快照版本
在 Maven 中,任何一个项目或者构件都必须有自己的版本。版本的值可能是 1.0.0、1.3-alpha-4、2.0、2.1-SNAPSHOT 或者 2.1-20091214.221414-13。其中,1.0.0、1.3-alpha-4 和 2.0 是稳定的发布版本,而 2.1-SNAPSHOT 和 2.1-20091214.221414-13 是不稳定的快照版本。快照版本只应该在组织内部的项目或模块间依赖使用。
将 Maven 项目设置为 SNAPSHOT 标识快照版本,当其发布到私服中时,Maven 会为其打上时间戳,如 2.1-SNAPSHOT 被修改为 2.1-20091214.221414-13,表示 2009 年 12 月 14 日 22 点 14 分 14 秒的第 13 次快照。当有项目依赖该快照版本时,Maven 会自动从仓库中检查该依赖的 SNAPSHOT 的最新构件,当发现有更新时便进行下载。默认情况下,Maven 每天检查一次更新,
4.4.2.依赖版本解析机制
当本地仓库没有依赖构件的时候,Maven 会自动从远程仓库下载;当依赖版本为快照版本的时候,Maven 会自动找到最新的快照。这背后的依赖解析机制可以概括如下:
- 当依赖的范围是 system 的时候,Maven 直接从本地文件系统解析构件。
- 根据依赖坐标计算仓库路径后,尝试直接从本地仓库寻找构件,如果发现相应构件,则解析成功。
- 在本地仓库不存在相应构件的情况下:
- 如果依赖的版本是显式的发布版本构件,如 1.2、2.1-beta-1 等,则遍历所有的远程仓库,发现后,下载并解析使用。
- 如果依赖的版本是 RELEASE 或者 LATEST,则基于更新策略读取所有远程仓库的元数据 groupId/artifactId/maven-metadata.xml,将其与本地仓库的对应元数据合并后,计算出 RELEASE 或者 LATEST 真实的值,然后基于这个真实的值检查本地和远程仓库,回到第 2 步重新执行。
- 如果依赖的版本是 SNAPSHOT,则基于更新策略读取所有远程仓库的元数据 groupId/artifactId/version/maven-metadata.xml,将其与本地仓库的对应元数据合并后,得到最新快照版本的值,然后基于该值检查本地仓库,或者从远程仓库下载。
- 如果最后解析得到的构件版本是时间戳格式的快照,如1.4.1-20091104.121450-121,则复制其时间戳格式的文件至非时间戳格式,如 SNAPSHOT,并使用该非时间戳格式的构件。
当依赖的版本不明晰的时候,如 RELEASE、LATEST 和 SNAPSHOT,Maven 就需要基于更新远程仓库的更新策略来检查更新。releases、snapshots 中 enabled 设置了对于发布版本、快照版本的支持,updatePolicy 设置了更新频率。用户也可以使用命令行 -U 参数强制让 Maven 检查更新,如mvn clean install -U
。
当 Maven 检查完更新策略,并决定检查依赖更新的时候,就需要检查仓库元数据 maven-metadata.xml。RELEASE 和 LATEST 版本,它们分别对应了仓库中存在的该构件的最新发布版本和最新版本(包含快照)。需要注意的是,在依赖声明中使用 LATEST 和 RELEASE 是不推荐的做法,因为 Maven 随时都可能解析到不同的构件,且Maven不会明确告诉用户这样的变化。为此,Maven3 不再支持在插件配置中使用 LATEST 和 RELEASE。当依赖的版本设为快照版本的时候,Maven也需要检查更新。
4.4.3.插件解析机制
插件的默认 groupId
如果插件为 Maven 的官方插件,groupId (org.apache.maven.plugins)可以省略。
解析插件前缀
Maven 引入了目标前缀的概念,help 是 maven-help-plugin 的目标前缀,dependency 是 maven-dependency-plugin 的前缀,有了插件前缀,Maven 就能找到对应的 artifactId,使得命令更简洁。插件前缀与 groupId:artifactId 是一一对应的,这种匹配关系存储在仓库元数据中。与之前提到的 groupId/artifactId/maven-metadata.xml 不同,这里的仓库元数据为 groupId/maven-metadata.xml。之前提到主要的插件都位于http://repo1.maven.org/maven2/org/apache/maven/plugins/和http://repository.codehaus.org/org/codehaus/mojo/,相应地,Maven 在解析插件仓库元数据的时候,会默认使用 org.apache.maven.plugins 和 org.codehaus.mojo 两个 groupId。也可以通过配置 settings.xml 让 Maven 检查其他 groupId 上的插件仓库元数据:
<pluginGroups>
<pluginGroup>com.your.plugins</pluginGroup>
</pluginGroups>
以下内容是从中央仓库的 org.apache.maven.plugins groupId 下插件仓库元数据中截取的一些片段,如果 org/apache/maven/plugins/maven metadata.xml 没有记录该插件前缀,则接着检查其他 groupId 下的元数据,如 org/codehaus/mojo/maven-metadata.xml,以及用户自定义的插件组。如果所有元数据中都不包含该前缀,则报错。
<metadata>
<plugins>
<plugin>
<name>Maven Clean Plugin</name>
<prefix>clean</prefix>
<artifactId>maven-clean-plugin</artifactId>
</plugin>
<plugin>
<name>Maven Compiler Plugin</name>
<prefix>compiler</prefix>
<artifactId>maven-compiler-plugin</artifactId>
</plugin>
<plugin>
<name>Maven Dependency Plugin</name>
<prefix>dependency</prefix>
<artifactId>maven-dependency-plugin</artifactId>
</plugin>
</plugins>
</metadata>
解析插件版本
Maven在超级POM中为所有核心插件设定了版本。
如果用户使用某个插件时没有设定版本,而这个插件又不属于核心插件的范畴,Maven 就会去检查所有仓库中可用的版本,然后做出选择。Maven 遍历本地仓库和所有远程插件仓库,将该路径下的仓库元数据归并后,就能计算出 latest 和 release 的值。在Maven2 中,插件的版本会被解析至 latest,也就是说,当用户使用某个非核心插件且没有声明版本的时候,Maven 会将版本解析为所有可用仓库中的最新版本,而这个版本也可能是快照版。Maven3 调整了解析机制,当插件没有声明版本的时候,不再解析至 latest,而是使用 release。
5.聚合和继承
5.1.聚合
聚合模块打包方式必须是 pom。聚合模块中,通过 POM 中的 module 元素设置模块,该元素值是模块 POM 相对于聚合模块 POM 的相对目录(一般来说,为了方便快速定位内容,模块所处的目录名称应当与其 artifactId 一致,不过这并不是硬性要求)。如:
<groupId>xyz.meyok.maven.account</groupId>
<artifactId>account-aggregator</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>account-email</module>
<module>account-persist</module>
</modules>
Maven 会首先解析聚合模块的 POM、分析要构建的模块、并计算出一个反应堆构建顺序(Reactor Build Order),然后根据这个顺序依次构建各个模块。反应堆是所有模块组成的一个构建结构。
5.2.继承
在 POM 中通过 parent 元素设置父 POM(父 POM 的打包类型必须是 pom),其中 relativePath 元素指定 pom.xml 文件相对地址,默认值为 ../pom.xml。如:
<parent>
<groupId>xyz.meyok.maven.account</groupId>
<artifactId>account-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../account-parent/pom.xml</relativePath>
</parent>
<artifactId>account-parent</artifactId>
当项目构建时,Maven 会首先根据 relativePath 检查父 POM,如果找不到,再从本地仓库查找。该值被设置为空时(<relativePath/>
),表示始终从仓库中获取,不从本地路径获取
POM 可继承的元素有:groupId、version、description、organization、inceptionYear、url、developers、contributors、distributionManagement、issueManagement、ciManagement、scm、mailingLists、properties、dependencies、dependencyManagement、repositories、build、reporting。
在 POM 中,pluginManagement 与 dependencyManagement 元素管理插件、依赖的版本,它实际不会导入相关构建,导入依然需要 dependencies 等元素。可以在父 POM 中使用 pluginManagement 与 dependencyManagement 元素进行设置,子 POM 再实际导入,这样可以省略版本等相关配置。
5.3.聚合与继承的关系
前者主要是为了方便快速构建项目,后者主要是为了消除重复配置。对于聚合模块来说,它知道有哪些被聚合的模块,但那些被聚合的模块不知道这个聚合模块的存在;对于继承关系的父 POM 来说,它不知道有哪些子模块继承于它,但那些子模块都必须知道自己的父 POM 是什么。聚合模块与继承关系中的父模块除了 POM 之外都没有实际的内容(两者打包类型均为 pom)。
5.4.反应堆
在一个多模块的 Maven 项目中,反应堆(Reactor)是指所有模块组成的一个构建结构。对于单模块的项目,反应堆就是该模块本身;但对于多模块项目来说,反应堆就包含了各模块之间继承与依赖的关系,从而能够自动计算出合理的模块构建顺序。
反应堆的构建顺序:先按照 POM 依赖顺序,再按照 modules 声明顺序。
裁剪反应堆
仅仅构建完整反应堆中的某些个模块,需要实时地裁剪反应堆。相关参数有:
-pl
、--projects
:构建指定的模块,模块间用逗号分隔。-rf
、-resume-from
:从指定的模块构建反应堆。-am
、--also-make
:同时构建所列模块的依赖模块。-amd
、-also-make-dependents
:同时构建依赖于所列模块的模块
mvn clean install -pl account-email, account-persist
mvn clean install -rf account-email
mvn clean install -pl account-email -am
mvn clean install -pl account-parent -amd
mvn clean install -pl account-parent -amd -rf account-email
6.Maven 测试
6.1.maven-surefire-plugin
Maven 本身并不是一个单元测试框架,Java 世界中主流的单元测试框架为 JUnit 和 TestNG。Maven 所做的只是在构建执行到特定生命周期阶段的时候,通过插件来执行 JUnit 或者 TestNG 的测试用例,这一插件就是maven-surefire-plugin,可以称之为测试运行器(Test Runner),它能很好地兼容 JUnit 3、JUnit 4 以及 TestNG。
在默认情况下,maven-surefire-plugin 的 test 目标会自动执行测试源码路径下所有符合一组命名模式的测试类。这组模式为:
**/Test*.java
:任何子目录下所有命名以 Test 开头的 Java 类。**/*Test.java
:任何子目录下所有命名以 Test 结尾的 Java 类。**/*TestCase.java
:任何子目录下所有命名以 TestCase 结尾的Java类。
6.2.测试控制
跳过测试
-
要求跳过测试代码的编译和测试,可通过命令行插件目标参数、POM 中插件全局设置来设置:
mvn package -Dmaven.test.skip=true
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>2.1</version> <configuration> <skipTests>true</skipTests> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.5</version> <configuration> <skipTests>true</skipTests> </configuration> </plugin>
-
要求跳过测试,可通过命令行插件目标参数、POM 中插件全局设置来设置:
mvn package -DskipTests
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.5</version> <configuration> <skipTests>true</skipTests> </configuration> </plugin>
指定运行测试用例
mvn test -Dtest=RandomGeneratorTest
mvn test -Dtest=Random*Test
mvn test -Dtest=RandomGeneratorTest, AccountCaptchaServiceTest
test 参数没有设置类时会报错,不过这可通过设置 failIfNoTests 参数来避免
mvn test -Dtest -DfailIfNoTests=false
包含与排除测试用例
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.5</version>
<configuration>
<includes>
<include>**/*Tests.java</include>
</includes>
<excludes>
<exclude>**/*ServiceTest.java</exclude>
<exclude>**/TempDaoTest.java</exclude>
</excludes>
</configuration>
</plugin>
6.3.测试报告
默认情况下,maven-surefire-plugin 会在项目的 target/surefire-reports 目录下生成两种格式的错误报告:简单文本格式、与 JUnit 兼容的 XML 格式。后者主要是为了支持工具的解析。
测试覆盖率是衡量项目代码质量的一个重要的参考指标。Cobertura 是一个优秀的开源测试覆盖率统计工具,Maven 通过 cobertura-maven-plugin 与之集成,用户可以使用简单的命令为 Maven 项目生成测试覆盖率报告。如:
mvn cobertura:cobertura
会生成测试覆盖率报告,位于项目目录 target/site/cobertura/ 下的 index.html 文件。
6.4.重用测试代码
默认的打包行为是不会包含测试代码的,因此在使用外部依赖的时候,其构件一般都不会包含测试代码。
maven-jar-plugin 有两个目标,分别是 jar 和 test-jar,前者通过 Maven 的内置绑定在 package 生命周期阶段运行,其行为就是对项目主代码进行打包,而后者(默认绑定至 package 生命周期阶段)并没有内置绑定。可将后者绑定至 test-jar 生命周期阶段,使用该目标来打包测试代码:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.2</version>
<executions>
<execution>
<goals>
<goal>test-jar</goal>
</goals>
</execution>
</executions>
</plugin>
在其它项目中导入该依赖需要声明 type 为 test-jar。
<dependency>
<groupId>...</groupId>
<artifactId>...</artifactId>
<version>...</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>
Archetype
使用 Archetype 生成项目骨架
Maven3 使用mvn archetype:generate
,Maven2 使用mvn org.apache.maven.plugins:maven-archetype-plugin:2.0-alpha-5:generate
(groupId:artifactId:version:goal)。Maven3 执行前者不安全,因为没有指定 Archetype 插件的版本,Maven 会自动去下载最新的版本,进而可能得到不稳定的 SNAPSHOT 版本,导致运行失败。然而在 Maven3 中,即使用户没有指定版本,Maven 也只会解析最新的稳定版本。
mvn archetype:generate
使用后,会有一个默认的 archetype 为 maven-archetype-quickstart:version
。其会导入一个 junit 依赖,以及插件的设置。
许晓斌.Maven 实战[M].北京:机械工业出版社.2010:1-XXX. ↩︎