我们可以用本指南了解 mvn clean install
的真正作用。
mvn clean install
到底是干啥的?
简短答案
Apache Maven 是一种流行的构建工具,它可以对我们项目的 Java 源代码进行编译、测试,并转换成可执行的 Java 程序:要么是一个 .jar
文件,要么是一个 .war
文件。
mvn clean install
就是执行此操作的命令:
- 我们是在调用
mvn
这个可执行文件,也就是说需要已经安装好 Maven。 clean
命令会删除项目中所有之前编译过的 Java 源代码和资源(比如 .properties)。构建会从一张白纸开始。- 然后,
Install
会编译、测试、打包我们的 Java 项目,甚至安装/复制构建好的 .jar/.war 文件到我们的本地 Maven 仓库。
长的答案:
Maven 不只是 mvn clean install
这几行。如果想学习 Maven 的依赖管理和构建周期的工作原理,或者在使用时如何避免常见的陷阱,请继续阅读。
Maven 速成
Maven 是 Java 领域最受欢迎的构建工具之一(另一个是Gradle,还有一个老式的 Ant)。我们不仅可以用它来构建 Java 项目,还可以用它构建用 JVM 语言编写的每个项目,比如 Kotlin 或者 Scala,以及其它编程语言,比如 C# 和 Ruby。
那么,构建工具到底是干嘛的?Maven 擅长做三件事情:
- 依赖管理:Maven 让我们可以轻松地在项目中包含第三方依赖(比如像 Spring 这样的库或者框架)。其他语言中的对等物是Javascript 的 npm,Ruby 的 gems,PHP 的 composer。
- 按约定编译:理论上讲,我们可以用 javac 命令行编译器手动编译带有大量类的大型 Java 项目(或使用 bash 脚本自动进行编译)。不过,这仅适用于玩具项目。Maven 期望 Java 源代码可以使用特定的目录结构,并且当我们稍后进行
mvn clean install
时,为我们完成整个编译和打包工作。 - 一切 Java: Maven 还可以通过插件运行代码质量检查,执行测试案例,甚至将应用程序部署到远程服务器。几乎是我们可以想到的所有可能的任务。
Maven 的目的布局
Maven 的 pom.xml
从技术上讲,任何包含 pom.xml
文件的目录都可以是一个有效的 Maven 项目。pom.xml 文件中包含了描述 Java 项目所需的一切。下面我们来看一个最小的版本:
<?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>com.marcobehler</groupId>
<artifactId>my-project</artifactId> (1)
<version>1.0-SNAPSHOT</version> (2)
<properties>
<maven.compiler.source>1.8</maven.compiler.source> (3)
<maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency> (4)
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
(1) 我们在定义一个叫做 ‘my-project’ 的项目。
(2) 版本号为1.0-SNAPSHOT,即在进行中。
(3) 使用Java 1.8进行编译。
(4) 带有单元测试所需的一个依赖:junit 版本 4.12。
Maven 的 src 和 target 文件夹
除了 pom.xml 文件外,只要我们在调用 mvn clean install
,我们还需要让 Maven 发挥作用的 Java 源代码。按惯例:
Java 源代码应该放在 “/src/main/java” 文件夹中。
Maven 会把编译后的 Java 类放入 “target/classes” 文件夹。
Maven 会根据项目,构建一个 .jar 或者 .war 文件,放入 “target” 文件夹。
最后,我们的项目的目录结构会是这样的:
+ myproject
+ -- src
+ -- main
+ -- java
MyApp.java
+ -- target
+ -- classes (upon mvn compile)
MyApp.class
myproject.jar (upon mvn package or mvn install)
pom.xml
现在我们有一个 pom.xml 文件 以及一个 src 文件夹,还需要 Maven 安装在电脑上。下面我们开始干活!
如何安装 Maven?
安装 Maven 超级简单。与 Java 类似,它只是一个 .zip 文件,我们需要把它下载下俩,放在硬盘上任意地方。这个 .zip 文件的内容应该是这样的:
Directory c:\dev\apache-maven-3.6.3
12.07.2019 09:25 <DIR> .
12.07.2019 09:25 <DIR> ..
12.07.2019 09:25 <DIR> bin
12.07.2019 09:25 <DIR> boot
12.07.2019 09:25 <DIR> conf
12.07.2019 09:25 <DIR> lib
12.07.2019 09:24 13.437 LICENSE
12.07.2019 09:24 182 NOTICE
12.07.2019 09:24 2.533 README.txt
现在我们需要确保把 /bin
目录添加到 PATH 变量中,否则就没法在任何地方调用 mvn
。就这样。
Maven 构建生命周期:各个阶段
当在项目中执行 mvn clean install
的时候,到底发生了什么?Maven 有一个构建生命周期的概念,这个生命周期是由不同阶段组成的。
如下是 Maven 默认的生命周期(注意,漏掉了 ‘clean’):
这些阶段是连续的,并且相互依赖。
举个例子:
当调用 mvn deploy
时,mvn 会也执行 deploy
之前的每个生命周期阶段,顺序是:validate
, compile
, test
, package
, verify
, install
。
verify
是一样的:validate
, compile
, test
, package
。所有其它阶段都是一样的。
而且由于 clean
不是 Maven 默认生命周期的一部分,因此我们最终会得到诸如 mvn clean install
或 mvn clean package
之类的命令。 Install
或 package
将触发所有之前的阶段,但是还是得指定 clean
。
Maven 在哪里存储第三方库?
其它语言会把项目依赖放在项目目录中,而 Maven 有仓库的概念。
有本地仓库(在用户的主目录下:~/.m2/),有远程仓库。远程仓库可以是公司内部的存储库(比如,Artifactory 和 Nexus),也可以是 https://repo.maven.apache.org/maven2/ 上的(引用)全局仓库。
Maven 总是会先把项目依赖下载到本地 Maven 仓库,然后在构建时引用它们。我们回想一下之前的 pom.xml 文件:
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
一旦我们对项目执行 “mvn test”,那么 mvn 就会把 junit 依赖下载到 ~/.m2/repository/junit/junit/4.12/junit-4.12.jar
,并且在构建时通过 Java 的类路径机制引用它。
有关使用仓库的更多信息,请参见官方文档:https://maven.apache.org/guides/introduction/introduction-to-repositories.html。
常见的 Maven 问题
在哪里可以找到任何第三方库的 Maven 坐标?
流行的网站是 https://mvnrepository.com/ 或者 https://search.maven.org/。在Stackoverflow上的这个话题中可以找到其它的网站。
mvn clean package 和 mvn clean install 之间有什么区别?
如果按照本指南进行操作,那么现在应该已经很清楚了。
- clean:删除 /target 文件夹。所以两条命令的结果是一样。
- package:将 .java 源代码转换成一个 .jar 或者 .war 文件,然后把它放到 /target 文件夹。
- install:首先,它打包。然后把 .jar/.war 文件放到本地 Maven 仓库(~/.m2/repository)中。
为什么我需要调用 ‘clean’? mvn install 难道不够吗?
从理论上讲,如果 Maven 足够聪明地执行可靠的增量构建,那么调用 mvn install
就足够了。这意味着要弄清楚哪些 Java 源文件/模块已更改,并且仅对修改过的进行编译。
在 Maven 3.1 之前,增量编译基本上是不存在的,甚至用最新的 Maven 和编译器插件版本,增量编译支持中也存在错误和文档问题。
因此,开发人员根深蒂固地总是调用 ‘mvn clean install’(即使这会增加大型项目的构建时间)。
为什么 mvn clean install 不会创建同级项目?
不幸的是,在多模块项目中会有一些事情发生,而其它构建工具(例如 Gradle)就没问题。想象一下,我们的 Maven 构建看起来像这样:
+ stocks-broker-app (父)
+ stocks-data-module
+ stocks-rest-module (依赖于 stock-data)
+ stocks-ui-module (依赖于 stock-data)
而当做如下事情时:
cd stocks-ui-module
mvn clean install // 或另一个适当的目标
问题是:Maven 不够聪明,没法自动构建 stocks-ui-module
所需的同级依赖 stock-data
。所以我们需要自己先把 stock-data
构建好。
不过有一种解决方法。我们不一定总是要构建整个父项目,而是可以指定要构建的子模块列表,并执行一次依赖的构建。 在我们的例子中,这看起来像:
cd .. // 这样就在父项目中
mvn -pl stocks-ui-module -am clean install // 或者另一个适当的目标
-pl
指定要构建的子模块。-am
代表also make
(也构建),会也构建依赖的模块。
mvn -U
是干嘛的?
有时,如果项目依赖于 SNAPSHOT 依赖,那么 Maven 就不会使用最新的快照版本更新本地 Maven 仓库。
这有点过于简化,不过如果要确保 Maven 始终尝试下载最新的快照依赖版本,请带 -U
开关调用它:
mvn -U clean install
mvnw 是什么?
有些项目带有 mvnw 可执行文件,它不代表 Windows 上的 Maven,而是代表 Maven 包装器。
这意味着您不必在计算机上安装 mvn 来构建项目,而是将 mvn 嵌入在项目目录中,然后可以用 mvnw 可执行文件调用它。
更多有关 Maven 包装器的信息,请参考https://github.com/takari/maven-wrapper。
结尾
本指南只是对Maven可以为您做的事情以及如何解决其最常见的陷阱的简要概述。