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"
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 包含了一个项目所有的配置,如项目的类型、名字、依赖关系等等。
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
用 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 个依赖范围:
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
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
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>
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