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中的配置,可以在settings.xml文件中配置代理。

可以使用类似于下面的内容来设置proxy:

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

Last updated: <2017-12-13 Wed 13:40>

Creator: Emacs 25.3.1 (Org mode 9.1.4)