「Maven」- 概念术语

问题描述

官方 Maven – Maven in 5 Minutes 文档,其展示 Maven 快速开始的方法;

该笔记将记录:我们将围绕该文档,学习 Maven 的概念术语,形成 Maven 的基本认识;

解决方案

我们摘抄 Maven in 5 Minutes 文档的部分内容,以便于逐步展开对概念术语的学习;

第一步、创建项目

mvn archetype:generate                                     \
    -DgroupId=com.mycompany.app                            \
    -DartifactId=my-app                                    \
    -DarchetypeArtifactId=maven-archetype-quickstart       \
    -DarchetypeVersion=1.4                                 \
    -DinteractiveMode=false

目录结构:

my-app
|-- pom.xml
`-- src
    |-- main
    |   `-- java
    |       `-- com
    |           `-- mycompany
    |               `-- app
    |                   `-- App.java
    `-- test
        `-- java
            `-- com
                `-- mycompany
                    `-- app
                        `-- AppTest.java

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/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>                        // POM Model 版本;针对 Maven 3 版本,固定写法;

  <groupId>com.mycompany.app</groupId>                      // 项目所属组;
  <artifactId>my-app</artifactId>                           // 项目唯一标识;
  <version>1.0-SNAPSHOT</version>                           // 项目版本;SNAPSHOT 表示快照,即开发中,仅有语义;

  <properties>
    <maven.compiler.source>1.7</maven.compiler.source>
    <maven.compiler.target>1.7</maven.compiler.target>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>                                   // 表示该依赖仅对 test 有效,如果在其他地方引用 JUnit,将产生错误;
    </dependency>
  </dependencies>
</project>

第二步、编译项目

# mvn compile
[INFO] Scanning for projects...
[INFO]
[INFO] ----------------------< com.mycompany.app:my-app >----------------------
[INFO] Building my-app 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-resources-plugin:3.0.2:resources (default-resources) @ my-app ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /tmp/demo/my-app/src/main/resources
[INFO]
[INFO] --- maven-compiler-plugin:3.8.0:compile (default-compile) @ my-app ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to /tmp/demo/my-app/target/classes
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.018 s
[INFO] Finished at: 2022-12-18T19:11:05+08:00
[INFO] ------------------------------------------------------------------------

Maven 本身并不具备编译功能,针对的代码的编译需要调用插件来完成,而插件版本与 JDK 有关联关系的:

// 针对 Java 11 环境,使用 maven-compiler-plugin 3.8.1 版本;

    <properties>
        <maven.compiler.release>11</maven.compiler.release>
    </properties>

    <build>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.8.1</version>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>

// 部分语法对 Java 版本也有要求,也可以进行修改

    <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>

第三步、测试项目

# mvn test
[INFO] Scanning for projects...
[INFO]
[INFO] ----------------------< com.mycompany.app:my-app >----------------------
[INFO] Building my-app 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
Downloading from central: https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-resources-plugin/3.0.2/maven-resources-plugin-3.0.2.pom
...
Downloaded from central: https://repo.maven.apache.org/maven2/junit/junit/4.11/junit-4.11.jar (245 kB at 469 kB/s)
[INFO]
[INFO] --- maven-resources-plugin:3.0.2:resources (default-resources) @ my-app ---
Downloading from central: https://repo.maven.apache.org/maven2/org/codehaus/plexus/plexus-component-annotations/1.6/plexus-component-annotations-1.6.pom
...
Downloaded from central: https://repo.maven.apache.org/maven2/org/codehaus/plexus/plexus-interpolation/1.24/plexus-interpolation-1.24.jar (79 kB at 70 kB/s)
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /tmp/demo/my-app/src/main/resources
[INFO]
[INFO] --- maven-compiler-plugin:3.8.0:compile (default-compile) @ my-app ---
Downloading from central: https://repo.maven.apache.org/maven2/org/codehaus/plexus/plexus-component-annotations/1.7.1/plexus-component-annotations-1.7.1.pom
...
Downloaded from central: https://repo.maven.apache.org/maven2/org/codehaus/plexus/plexus-compiler-javac/2.8.4/plexus-compiler-javac-2.8.4.jar (21 kB at 27 kB/s)
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to /tmp/demo/my-app/target/classes
[INFO]
[INFO] --- maven-resources-plugin:3.0.2:testResources (default-testResources) @ my-app ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /tmp/demo/my-app/src/test/resources
[INFO]
[INFO] --- maven-compiler-plugin:3.8.0:testCompile (default-testCompile) @ my-app ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to /tmp/demo/my-app/target/test-classes
[INFO]
[INFO] --- maven-surefire-plugin:2.22.1:test (default-test) @ my-app ---
Downloading from central: https://repo.maven.apache.org/maven2/org/apache/maven/surefire/maven-surefire-common/2.22.1/maven-surefire-common-2.22.1.pom
...
Downloaded from central: https://repo.maven.apache.org/maven2/org/apache/maven/surefire/surefire-junit4/2.22.1/surefire-junit4-2.22.1.jar (85 kB at 278 kB/s)
[INFO]
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.mycompany.app.AppTest
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.037 s - in com.mycompany.app.AppTest
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  23.773 s
[INFO] Finished at: 2022-12-18T18:49:03+08:00
[INFO] ------------------------------------------------------------------------

第三步、打包项目

# mvn package
[INFO] Scanning for projects...
[INFO]
[INFO] ----------------------< com.mycompany.app:my-app >----------------------
[INFO] Building my-app 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
Downloading from central: https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-jar-plugin/3.0.2/maven-jar-plugin-3.0.2.pom
...
Downloaded from central: https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-jar-plugin/3.0.2/maven-jar-plugin-3.0.2.jar (27 kB at 75 kB/s)
[INFO]
[INFO] --- maven-resources-plugin:3.0.2:resources (default-resources) @ my-app ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /tmp/demo/my-app/src/main/resources
[INFO]
[INFO] --- maven-compiler-plugin:3.8.0:compile (default-compile) @ my-app ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-resources-plugin:3.0.2:testResources (default-testResources) @ my-app ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /tmp/demo/my-app/src/test/resources
[INFO]
[INFO] --- maven-compiler-plugin:3.8.0:testCompile (default-testCompile) @ my-app ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-surefire-plugin:2.22.1:test (default-test) @ my-app ---
[INFO]
[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.mycompany.app.AppTest
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.039 s - in com.mycompany.app.AppTest
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO]
[INFO] --- maven-jar-plugin:3.0.2:jar (default-jar) @ my-app ---
Downloading from central: https://repo.maven.apache.org/maven2/org/apache/maven/maven-archiver/3.1.1/maven-archiver-3.1.1.pom
...
Downloaded from central: https://repo.maven.apache.org/maven2/org/apache/commons/commons-compress/1.11/commons-compress-1.11.jar (426 kB at 370 kB/s)
[INFO] Building jar: /tmp/demo/my-app/target/my-app-1.0-SNAPSHOT.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  7.337 s
[INFO] Finished at: 2022-12-18T18:52:13+08:00
[INFO] ------------------------------------------------------------------------

Coordinate and Dependency

Coordinate

坐标,其通过 groupId artifactId version packaging classifier 来描述某个唯一的构件,而后 Maven 便能通过坐标到仓库中下载该构件;

通过 groupId、artifactId、version 三个属性就能定位一个构件;

示例:

<groupId>com.mycompany.app</groupId>
<artifactId>my-app</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>

groupId:定义当前 Maven 项目隶属的实际项目。首先,Maven 项目和实际项目不一定是一对一的关系。比如 SpringFramework 这一实际项目,其对应的 Maven 项目会有很多,如 spring-core 、spring-context 等。这是由于 Maven 中模块的概念,所以一个实际项目往往会被划分成很多模块。其次,groupld 不应该对应项目隶属的组织或公司。原因很简单,一个组织下会有很多实际项目,如果 groupld 只定义到组织级别,而后面我们会看到,artifactld 只能定义 Maven 项目 (模块) ,那么实际项目这个层将难以定义。最后,groupld 的表示方式与 Java 包名的表示方式类似,通常与反向域名对应;

artifactId:该元素定义实际项目中的一个 Maven 项目 (模块) ,推荐的做法是使用实际项目名称作为 artifactld 的前级。加入 artifactId 是 nexus-indexer,使用了实际项目名 nexus 作为前缀,这样做的好处是方便寻找实际构件。在默认情况下,Maven 生成的构件,其文件名会以 artifactld 作为开头,如 nexus-indexer-2.0.0.jar,使用实际项目名称作为前缀之后,就能方便从一个 lib 文件夹中找到某个项目的一组构件;

version:该元素定义 Maven 项目当前所处的版本。需要注意的是,Maven 已定义一套完成的版本规范,以及快照 (SNAPSHOT) 的概念;

packaging:该元素定义 Maven 项目的打包方式。首先,打包方式通常与所生成构件的文件扩展名对应,如上例中 packaging 为 jar,最终的文件名为 .jar(未定义 packaging 时的默认行为),而使用 war 打包方式的 Maven 项目,最终生成的构件会有一个 .war 文件,不过这不是绝对的。其次,打包方式会影响到构建的生命周期,比如 jar 打包和 war 打包会使用不同的命令;

classifier:该元素用来帮助定义构建输出的一些附属构件。附属构件与主构件对应,例如主构件是 nexus-indexer-2.0.0.jar,该项目可能还会通过使用一些插件生成如 nexus-indexer-2.0.0-javadoc.jar 、nexus-indexer-2.0.0-sourees.jar 这样一些附属构件,其包含了 Java 文档和源代码。这时候,javadoc 和 sources 就是这两个附属构件的 classifier。这样,附属构件也就拥有了自己唯一的坐标。还有一个关于 classifier 的典型例子是 TestNG,TestNG 的主构件是基于 Java 1.4 平台的,而它又提供了一个 classifier 为 jdk 的附属构件。注意,不能直接定义项目的 classifier,因为附属构件不是项目直接默认生成的,而是由附加的插件帮助生成;

构件名称与坐标的关系:artifactId-version[-classifier].packaging

Dependency

示例:

<dependencies>
	<dependency>
	  <groupId>junit</groupId>
	  <artifactId>junit</artifactId>
	  <version>4.12</version>
	  <scope>test</scope>
	</dependency>
</dependencies>

示例如上,依赖将通过如下参数定义:
1)groupId、artifactId、version:坐标
2)type:类型,默认为 jar 类型;
3)scope:依赖范围;
4)optional:标记该依赖是否为可选依赖;
5)exclusions:其用于排除某个传递依赖;

Plugin and Goal

Plugin

诸如 maven-resources-plugin 、maven-surefire-plugin 等等,都是插件,Maven 将通过这些插件来完成具体的任务;

maven-surefire-plugin,是 Maven 用于执行测试的插件;

Goal

诸如 default-resources、default-testResources 等等,被成为 Goal(目标?),理解为由 Plugin 提供的具体功能即可;

[INFO] — maven-compiler-plugin:3.8.0:testCompile (default-testCompile) @ my-app —
执行 maven-compiler-plugin 插件提供的 default-testCompile 目标

Suffice it to say for now that a plugin is a collection of goals with a general common purpose.
所以,实际的 mvn 构建,就是各个 Goal 的依序执行;

Phase、Goal

Phase

针对 mvn package 命令,其 package 是众多 Phase 之一,属于 Build Lifecycle(构建生命周期);

Goal

当给定一个 Phase 时,Maven 执行序列中的每个 Phase,直到并包括定义的 Phase。假如我们按照上述顺序定义 Phase,当执行 package 阶段时,实际执行的阶段是:validate、compile、test、package;

Phase 实际上被映射到底层的多个 Goal,但每个 Phase 所执行的 Goal 取决于项目类型;

Phase and Goal

例如 mvn clean dependency:copy-dependencies package 命令,Phase 与 Goal 能够在命令行中顺序执行;

SNAPSHOT

不同的模块,由不同人员负责开发,但是模块之间存在依赖关系,而开发工作又是同时进行的。

此时上游可以发布快照版本,下游使用该模块的快照版本。同时,快照版本不需要更新版本号,其在发布是会包含时间戳信息,此时下游使用时将自动根据时间戳更新。

快照版本只应该在组织内部的项目或模块间依赖使用,因为这时,组织对于这些快照版本的依赖具有完全的理解及控制权。项目不应该依赖于任何组织外部的快照版本依赖, 由于快照版本的不稳定性,这样的依赖会造成潜在的危险。也就是说,即使项目构建今天是成功的,由于外部的快照版本依赖实际对应的构件随时可能变化,项目的构建就可能由于这些外部的不受控制的因素而失败;

当项目经过完善的测试后需要发布的时候,就应该将快照版本更改为发布版本。例如, 将 2.1-SNAPSHOT 更改为 2.1,表示该版本已经稳定,且只对应了唯一的构件。相比之下, 2.1-SNAPSHOT 往往对应了大量的带有不同时间戳的构件,这也决定了其不稳定性;

参考文献

Maven – Maven in 5 Minutes