Apache Maven

Table of Contents

1. Maven 简介

Apache Maven 是一个项目管理和构建自动化工具。它可以自动从 Repositories 中下载项目中配置的依赖库(如 JUnit 等)到本地仓库。

参考:
Maven Documentation: http://maven.apache.org/guides/

1.1. 安装

从官网下载 Maven 包后,解压后,把其 bin 子目录加入到环境变量 PATH 中即可使用。

可以用命令 mvn -v 来测试安装是否成功,如:

$ mvn -v
Apache Maven 3.3.3 (7994120775791599e205a5524ec3e0dfe41d4a06; 2015-04-22T04:57:37-07:00)
Maven home: /opt/apache-maven-3.3.3
Java version: 1.8.0_45, vendor: Oracle Corporation
Java home: /Library/Java/JavaVirtualMachines/jdk1.8.0_45.jdk/Contents/Home/jre
Default locale: en_US, platform encoding: UTF-8
OS name: "mac os x", version: "10.8.5", arch: "x86_64", family: "mac"

参考:https://maven.apache.org/install.html

1.2. 命令行基本使用

mvn 命令的基本格式如下:

$ mvn [options] [<goal(s)>] [<phase(s)>]

可以用下面命令得到选项的帮助文档:

$ mvn -h

1.3. 实例:Hello World 工程

1.3.1. 从模板创建工程

可以使用下面命令来创建一个包含 Hello World 程序的简单工程(当然也可以手动创建,只是麻烦些):

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

第一次运行比较慢,因为它要去 repositories 中下载一些运行需要的包到本地仓库。
上面命令运行成功后,会生成工程的目录结构,它是一个 maven standard project structure ,这个例子中是这样:

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

自动生成了 Project Object Model 文件 pom.xml,它是 maven 的工程文件(在 maven 1 中这个文件名为 project.xml,从 maven 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">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.mycompany.app</groupId>
  <artifactId>my-app</artifactId>
  <packaging>jar</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>my-app</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>

同时,自动生成了一个 Hello World Java 程序,内容如下:

$ cat src/main/java/com/mycompany/app/App.java
package com.mycompany.app;

/**
 * Hello world!
 *
 */
public class App
{
    public static void main( String[] args )
    {
        System.out.println( "Hello World!" );
    }
}

1.3.2. 编译和测试运行

进入到工程所在目录后,用命令 mvn compile 可以编译工程。如:

$ cd my-app
$ mvn compile

编译完成后,使用 mvn exec:java 可以运行程序。如:

$ mvn --quiet exec:java -Dexec.mainClass=com.mycompany.app.App
Hello World!

注:通过 -Dexec.args 可以传递参数,如: -Dexec.args="arg0 arg1"

1.3.3. 打包和运行

进入到工程所在目录后,用命令 mvn package 可以打包工程。如:

$ cd my-app
$ mvn package

运行编译生成的 jar 包:

$ java -cp target/my-app-1.0-SNAPSHOT.jar com.mycompany.app.App
Hello World!

1.4. Maven Repositories

Maven 会自动从远程 Repositories 中下载依赖包到本地仓库。本地仓库位于:

~/.m2/repository                  # Unix-like system
C:\Users\username\.m2\repository  # Windows

2. Project Object Model(pom.xml)

简单地说,pom.xml 包含了一个项目所有的配置,如项目的类型、名字、依赖关系等等。

参考:
Introduction to the POM
POM Reference

2.1. 最小的 pom 文件

下面是一个 minimum pom 文件的例子:

<project>
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.mycompany.app</groupId>
  <artifactId>my-app</artifactId>
  <version>1</version>
</project>

其中, groupId, artifactId, version 必须存在,它们用来标识一个工程。

2.2. Project Inheritance

参考:https://maven.apache.org/guides/introduction/introduction-to-the-pom.html#Project_Inheritance

工程比较大时,往往分成几个子工程。每个子工程都有自己的 POM 文件,设置它们为父子关系,构造父工程就会构建所有的子工程。子工程的 POM 会继承父工程的 POM 中的设置。

所有的 POM 都继承于 Super POM,它设置了一些默认值。
有效 POM(Effective POM)就是 Project POM 加上 Supper POM,可以用命令 mvn help:effective-pom 查看有效 POM。

下面这些元素会在父子工程中合并:

  • dependencies
  • developers and contributors
  • plugin lists (including reports)
  • plugin executions with matching ids
  • plugin configuration
  • resources

2.2.1. 工程继承实例

假设工程结构如下,

.
 |-- my-module
 |   `-- pom.xml
 `-- pom.xml

子工程中可以通过 parent 元素来指定父工程。

父 POM 和子 POM 分别如下:

$ cat pom.xml
<project>
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.mycompany.app</groupId>
  <artifactId>my-app</artifactId>
  <version>1</version>
</project>

$ cat my-module/pom.xml
<project>
  <parent>
    <groupId>com.mycompany.app</groupId>
    <artifactId>my-app</artifactId>
    <version>1</version>
  </parent>

  <modelVersion>4.0.0</modelVersion>
  <groupId>com.mycompany.app</groupId>
  <artifactId>my-module</artifactId>
  <version>1</version>
</project>

子项目 POM 的 groupId 和 version 可以继承自父 POM,所以可以省略。即也可以写为下面形式:

$ cat my-module/pom.xml
<project>
  <parent>
    <groupId>com.mycompany.app</groupId>
    <artifactId>my-app</artifactId>
    <version>1</version>
  </parent>

  <modelVersion>4.0.0</modelVersion>
  <artifactId>my-module</artifactId>
</project>

说明:如果父工程的位置不在其父目录,可以通过 relativePath 指定父工程相对位置。

...
  <parent>
    <groupId>com.mycompany.app</groupId>
    <artifactId>my-app</artifactId>
    <version>1</version>
    <relativePath>../relative/path/to/parent/pom.xml</relativePath>
  </parent>
...

2.3. Dependency Mechanism

参考:https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html#Importing_Dependencies

用 Maven 可以方便地管理依赖关系。依赖关系定义在 POM.xml 文件的 dependencies 元素中。
如,前面介绍的 Hello World 工程的 POM.xml 文件中有下面片断:

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

在 Maven 中,依赖是传递的。假设工程 A 依赖于库 B,而库 B 依赖于库 C。这时工程 A 显然也会依赖于库 C,但我们在工程 A 的配置中 只需要指定它的直接依赖关系(即库 B),而不用指定它的间接依赖(即库 C) ,Maven 会自动找到库 C(这个信息可以从库 B 的配置中找到)。如,前面例子中 junit 其实也会依赖于其它的包,但我们不需要关心它,如果本地库中不存在,Maven 会自动下载它们。

2.3.1. Dependency Scope

dependency 标签的子标签 scope 决定了依赖关系的适用范围。

Maven 中,支持下面 6 个依赖范围:

Table 1: Scopes in Maven
Scope Description
compile This scope indicates that dependency is available in classpath of project. It is default scope.
provided This scope indicates that dependency is to be provided by JDK or web-Server/Container at runtime
runtime This scope indicates that dependency is not required for compilation, but is required during execution.
test This scope indicates that the dependency is only available for the test compilation and execution phases.
system This scope is similar to provided except that you have to provide the JAR which contains it explicitly.
import This scope is only used when dependency is of type pom in the <dependencyManagement> section. This scopes indicates that the specified POM should be replaced with the dependencies in that POM's <dependencyManagement> section.

如,前面的例子中 junit 的 scope 设置的是 test,那么它只会在执行 compiler:testCompile 等目标的时候才会被加入到 classpath 中,在执行 compiler:compile 等目标时是不会引入 junit 的。

如,我们在编译 servlet 程序时需要对应的 servlet-api.jar 文件,但打包时并不需要把这个 jar 文件放入到 WAR 中,因为运行时 servlet 容器会提供它。这时,我们可以把依赖 scope 设置为 provided,意思是 JDK 或者容器会提供所需的 jar 文件。

2.3.2. Dependency Management

The dependency management section is a mechanism for centralizing dependency information.

假设有多个子项目,如 sub1,sub2,他们都依赖于同一个库 lib1,我们可以把 lib1(包含其 version 和 scope 等信息)指定到 sub1 和 sub2 的 POM 文件中。

但是,一旦我们想要升级 lib1 的版本,我们不得不去每个子项目的 POM 文件中做相应的修改。更好的办法是使用 dependencyManagement。

一般来说,会在项目最顶层的父 POM 中看到 dependencyManagement 标签。dependencyManagement 标签可以把依赖库的相关信息(如 version,scope 等)放到一个中心位置。在子项目中引用依赖时不用显式地指定 version,scope 等信息了。Maven 会沿着父子层次向上走,直到找到一个拥有 dependencyManagement 标签的项目,然后就使用在这个 dependencyManagement 标签中指定的 version,scope 等信息。

说明:dependencyManagement 标签里只是声明依赖,并不真正引入它。子项目需要显式声明需要用的依赖。

如,前面例子中,父项目的 POM 中可以这样写:

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>com.xxx</groupId>
      <artifactId>lib1</artifactId>
      <version>1.2.3</version>
      <scope>test</scope>
    </dependency>
  <dependencies>
</dependencyManagement>

子项目 sub1 和 sub2 的 POM 中可以这样指定依赖(不用指定 version 等信息了):

<dependencies>
  <dependency>
    <groupId>com.xxx</groupId>
    <artifactId>lib1</artifactId>
  </dependency>
</dependencies>

如果要修改依赖库的相关信息,修改父项目 POM 中 dependencyManagement 中的内容即可,子项目的 POM 文件不用改变。

3. Build Lifecycle

Maven 中的生命周期(Lifecycle)由多个阶段(phases)组成。

Maven 中内置的 3 个相互独立的 lifecycles 和它们所包含的 phases:

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, generate-test-resources, process-test-resources, test-compile, process-test-classes, test, prepare-package, package, pre-integration-test, integration-test, post-integration-test, verify, install, deploy
site
pre-site, site, post-site, site-deploy

当我们指定一个 phase 时,maven 会先依次执行这个 phase 所在 lifecycle 中之前的所有 phases,然后执行这个 phase 本身(这和执行 goal 是不同的,执行 goal 时只会执行 goal 本身)。 如:

$ mvn clean       # 依次执行生命周期clean中的阶段 pre-clean 和 clean
$ mvn package     # 依次执行生命周期default中的阶段 validate, initialize, ..., package

参考:
Introduction to the Build Lifecycle

4. Plugin 和 Goal

Maven plugin is a collection of one or more goals which do some task or job.

Maven 中,用 mvn <plugin-name>:<goal-name> 可以这样执行一个 goal,如:

$ mvn compiler:compile

注意:“执行 goal”和“执行 phase”有很大的不同:

When we invoke a goal directly, Maven executes just that goal, whereas when we invoke a lifecycle phase all the phases up to that phase are executed.

摘自:http://www.codetab.org/apache-maven-tutorial/maven-plugin-goals/

4.1. 查看 Plugin 有哪些 Goal

如何查看 Plugin 有哪些 Goal?

方法一:直接在官网上查看。http://maven.apache.org/plugins/index.html
方法二:用命令 mvn <plugin-name>:help 或者 mvn help:describe -Dplugin=<plugin-name> 。例如:

$ mvn help:describe -Dplugin=compiler
[INFO] org.apache.maven.plugins:maven-compiler-plugin:2.5.1

Maven Compiler Plugin
  The Compiler Plugin is used to compile the sources of your project.

This plugin has 3 goals:

compiler:compile
  Compiles application sources

compiler:help
  Display help information on maven-compiler-plugin.
  Call
    mvn compiler:help -Ddetail=true -Dgoal=<goal-name>
  to display parameter details.

compiler:testCompile
  Compiles application test sources.

5. 配置(settings.xml)

settings.xml 可以出现在下面两个位置中:

The Maven install: ${maven.home}/conf/settings.xml
A user’s install: ${user.home}/.m2/settings.xml

参考:Settings Reference: https://maven.apache.org/settings.html

5.1. 设置 proxy

maven 不会使用环境变量 http_proxy/https_proxy 中的配置,可以在 settings.xml 文件中配置代理。

可以使用类似于下面的内容来设置 proxy(设置了两个 proxy,分别用于 http 协议和 https 协议):

<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
                      https://maven.apache.org/xsd/settings-1.0.0.xsd">
  <proxies>
    <proxy>
      <id>myproxy1</id>
      <active>true</active>
      <protocol>http</protocol>
      <host>proxy.somewhere.com</host>
      <port>8080</port>
      <username>proxyuser</username>
      <password>somepassword</password>
      <nonProxyHosts>localhost|127.0.0.1</nonProxyHosts>
    </proxy>
    <proxy>
      <id>myproxy2</id>
      <active>true</active>
      <protocol>https</protocol>
      <host>proxy.somewhere.com</host>
      <port>8080</port>
      <username>proxyuser</username>
      <password>somepassword</password>
      <nonProxyHosts>localhost|127.0.0.1</nonProxyHosts>
    </proxy>
  </proxies>
</settings>

注:上面设置中,如果 proxy 服务器不需要 username, password 可以省略它们。

6. Tips

6.1. 引入仓库外的 jar 包

有时工程依赖的 jar 包(比如自己写了一个 ldapjdk.jar)不在公共仓库中,这时如何把它加入到 maven 的依赖中呢?

方法一(不推荐):把依赖配置为 system scope。
把依赖的 scope 中配置为 system,并且把依赖的 systemPath 中指定 jar 包的路径。如:

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

   ......
   <dependencies>
      ......
      <dependency>
         <groupId>test</groupId>
         <artifactId>ldapjdk</artifactId>
         <scope>system</scope>
         <version>1.0</version>
         <systemPath>${project.basedir}/lib/ldapjdk.jar</systemPath>
      </dependency>
   </dependencies>
</project>

注:使用 jar-with-dependencies 打包时,system scope 的 jar 包不会被包含,你可以通过配置复制 resources 来包含这个 jar 包。不推荐使用这种方式。
参考:https://www.tutorialspoint.com/maven/maven_external_dependencies.htm

方法二(推荐):建立本地仓库。
可以建立一个本地仓库,这样引入本地仓库中的依赖和引入公共仓库中的依赖完全相同。

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

   ......
   <dependencies>
      ......
      <dependency>
         <groupId>test</groupId>
         <artifactId>ldapjdk</artifactId>
         <version>1.0</version>
      </dependency>
   </dependencies>

  <repositories>
    <repository>
      <id>in-project</id>
      <name>In Project Repo</name>
      <url>file://${project.basedir}/lib</url>
    </repository>
  </repositories>
</project>

注:本地仓库中你 jar 包名字及路径需要遵循下面格式:

groupId/artifactId/version/artifactId-verion.jar

这个例子中,你需要确保仓库目录“${project.basedir}/lib”中存在文件“test/ldapjdk/1.0/ldapjdk-1.0.jar”。

6.2. 生成包含所有依赖的 jar 包

使用 maven-shade-plugin 可以生成包含所有依赖的 jar 包。

在 pom.xml 中增加下面内容即可:

    <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-shade-plugin</artifactId>
        <version>3.1.0</version>
        <executions>
          <execution>
            <phase>package</phase>
            <goals>
              <goal>shade</goal>
            </goals>
            <configuration>
              <transformers>
                <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                  <manifestEntries>
                    <Main-Class>com.youcompany.app.App</Main-Class>     <!-- 请修改它 -->
                  </manifestEntries>
                </transformer>
              </transformers>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>

运行 mvn package 后,可以得到包含所有依赖的 jar 包,这样可使用 java -jar 执行它,如: java -jar target/your-app-1.0.jar

参考:http://maven.apache.org/plugins/maven-shade-plugin/examples/executable-jar.html

6.3. 在命令行中控制日志输出级别

Maven 支持下面的命令行选项来控制日志输出的级别:

-e, --errors
Produce execution error messages
-X, --debug
Produce execution debug output
-q, --quiet
Quiet output, only show errors

参考:http://books.sonatype.com/mvnref-book/reference/running-sect-options.html#running-sect-verbose-option

6.4. 查看 Effective POM

有效 POM(Effective POM)就是 Project POM 加上 Supper POM,可以用下面命令查看有效 POM。

$ mvn help:effective-pom

6.5. 组织和编译多个模块

假设工程中有两个模块:my-app(jar)和 my-webapp(war),其中 my-webapp 又依赖于 my-app。则如何组织和编译它们呢?

一般,可按下面目录结构组织工程。

+- pom.xml
+- my-app
|  +- pom.xml
|  +- src
|    +- main
|      +- java
+- my-webapp
|  +- pom.xml
|  +- src
|    +- main
|      +- webapp

工程中,一共有 3 个 POM 文件(pom.xml, my-app/pom.xml, my-webapp/pom.xml)。

其中,pom.xml 称为父 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/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

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

  <modules>
    <module>my-app</module>
    <module>my-webapp</module>
  </modules>
</project>

在 my-app/pom.xml, my-webapp/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">
  <parent>
    <groupId>com.mycompany.app</groupId>
    <artifactId>app</artifactId>
    <version>1.0-SNAPSHOT</version>
  </parent>
  ...

此外,由于 my-webapp 依赖于 my-app,所以在 my-webapp/pom.xml 还应包含下面内容:

  ...
  <dependencies>
    <dependency>
      <groupId>com.mycompany.app</groupId>
      <artifactId>my-app</artifactId>
      <version>1.0-SNAPSHOT</version>
    </dependency>
    ...
  </dependencies>

参考:How do I build more than one project at once?

6.6. 增加 repository

要增加 repository,可以在工程的 pom.xml 文件中,或者~/.m2/settings.xml 文件中配置。

下面是在工程的 pom.xml 文件中增加 repository 的例子:

<project ...>
......
  <repositories>
    <repository>
      <id>your.customized.name</id>
      <url>https://maven.java.net/content/repositories/public/</url>
    </repository>
  </repositories>
</project>

参考:
https://maven.apache.org/guides/mini/guide-multiple-repositories.html

Author: cig01

Created: <2016-06-13 Mon>

Last updated: <2020-08-22 Sat>

Creator: Emacs 27.1 (Org mode 9.4)