Maven 知识点

发布时间 2023-12-23 18:33:25作者: MeYokYang

Maven[1]

1.基础知识

1.1.Maven 相关目录、文件

Maven 安装路径下,各目录、文件解析:

  • bin/:包含了mvn运行的脚本,这些脚本用来配置 Java 命令,准备好 classpath 和相关的 Java 系统属性,然后执行 Java 命令。

    mvn指令其后可添加插件目标或者生命周期阶段。

    mvnDebugmvn多了一条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、verifyinstalldeploy

    阶段 任务
    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 会自动找到最新的快照。这背后的依赖解析机制可以概括如下:

  1. 当依赖的范围是 system 的时候,Maven 直接从本地文件系统解析构件。
  2. 根据依赖坐标计算仓库路径后,尝试直接从本地仓库寻找构件,如果发现相应构件,则解析成功。
  3. 在本地仓库不存在相应构件的情况下:
    • 如果依赖的版本是显式的发布版本构件,如 1.2、2.1-beta-1 等,则遍历所有的远程仓库,发现后,下载并解析使用。
    • 如果依赖的版本是 RELEASE 或者 LATEST,则基于更新策略读取所有远程仓库的元数据 groupId/artifactId/maven-metadata.xml,将其与本地仓库的对应元数据合并后,计算出 RELEASE 或者 LATEST 真实的值,然后基于这个真实的值检查本地和远程仓库,回到第 2 步重新执行。
    • 如果依赖的版本是 SNAPSHOT,则基于更新策略读取所有远程仓库的元数据 groupId/artifactId/version/maven-metadata.xml,将其与本地仓库的对应元数据合并后,得到最新快照版本的值,然后基于该值检查本地仓库,或者从远程仓库下载。
  4. 如果最后解析得到的构件版本是时间戳格式的快照,如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 可继承的元素有:groupIdversion、description、organization、inceptionYear、url、developers、contributors、distributionManagement、issueManagement、ciManagement、scm、mailingLists、propertiesdependenciesdependencyManagementrepositoriesbuild、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 世界中主流的单元测试框架为 JUnitTestNG。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 依赖,以及插件的设置。


  1. 许晓斌.Maven 实战[M].北京:机械工业出版社.2010:1-XXX. ↩︎