背景
Maven 是 Apache 旗下的一款项目管理工具, 主要用于管理 Java 项目. 按照官网的解释, Maven 是意第绪语中 "知识的积累" 的意思.
为什么需要 Maven 以及 Maven 能干什么呢? 如果你了解甚至用过 C 语言开发中常用到的 Makefile 的话, 故事就很好展开了(关于 Makefile 的具体内容, 不属于这里的讨论范围).
首先, Makefile 可以算是最早的构建工具了. 主要使用来对 C/C++ 项目进行构建, 通过一些语法规则, 可以实现一些文件生成依赖顺序的管理一类的. 理论上来说, 使用 Makefile 也可以管理 Java 项目, 只是会比较繁琐. 于是 Java 社区参考 Makefile 的理念, 开发出了大家也应该较为熟知 Ant(Another Neat Tool), 用来管理 Java 项目, Ant 可以认为是 Java 社区的 Makefile, 使用 XML 文件来书写构建规则, 而不是 Makefile 自有的规则. Ant 相对于 Makefile 来说肯定是进步了, 更适合 Java 开发了, 但是对于每个项目来说, 依然有很多重复操作, 每个项目都需要去定义源文件位置, 目标文件夹, 等等等等. 基于这个背景, Maven 出现了. Maven 秉承的理念是: "约定大于配置". 反正每个项目都要定义源文件位置, 与其让大家自由发挥, 不如由我定死一个位置, 大家来遵守即可. 大家都遵守同样的规则的话, 可以节省大量对于路径这样的事情的学习时间. 于是 Maven 会默认要求 Java 项目的源文件出现在 src\main\java
下, 避免大家百花齐放(后面会讲到, 这个东西其实也可以改, 不过没有必要, 遵守约定就是最好的. 而且后面也会讲到, 这种约定是在哪来约定的). 顺便再讲讲, Maven 过后, 还有一个叫做 Gradle 的构建工具, 旨在提供比 Maven 更好的构建体验, 不过这不是本文重点.
简单总结下时间线:
- make 命令, Makefile. 主要用于 C/C++, 最早的构建工具. 1976 年.
- Ant, Java 版的 make 命令, Makefile. XML 文件编写构建规则. 2000 年.
- Maven, 约定大于配置的 Ant. 自由度降低, 重复工作变少. XML 文件编写构建规则. 2004 年.
- Gradle, 摒弃 XML 文件, 使用 Groovy 编写构建规则. 2007 年.
另外, 本文中讲到的几乎所有内容和概念, 都可以从 Maven实战 这本书中读到. 有时间和兴趣的建议直接阅读这本书, 会对 Maven 有更清晰的了解.
安装
Maven 其实没有安装程序, 就是一个压缩包, 解压缩即可.
到 Downloading Apache Maven 下载合适的压缩包, 解压缩到合适的地方, 我选择的是 C:\Program Files\Apache
这里, 因为 Apache 旗下的还有一款管理工具 Ant 也可以解压缩到同样的地方, 便于管理.
解压缩完添加一下环境变量, 没有什么具体的要求, 建议添加一个变量叫做 MAVEN_HOME
, 值为 C:\Program Files\Apache\apache-maven-3.8.1
. 然后添加 %MAVEN_HOME%\bin
到 Path
下即可. (MAVEN_HOME 没有直接设置到 bin 的原因是, 设置 Ant 的时候, ANT_HOME
就要求必须不能设置到 bin, 只能到上一层, 所以这里参考了 Ant 的设置).
设置完后, 打开 CMD, 输入 mvn -v
有显示即可.
C:\Users\zhanlin>mvn -v
Apache Maven 3.8.1 (05c21c65bdfed0f71a2f2ada8b84da59348c4c5d)
Maven home: C:\Program Files\Apache\apache-maven-3.8.1\bin\..
Java version: 16, vendor: Oracle Corporation, runtime: C:\Program Files\Java\jdk-16
Default locale: en_US, platform encoding: Cp1252
OS name: "windows 10", version: "10.0", arch: "amd64", family: "windows"
Maven 配置相关
本地仓库地址
正确安装 Maven 后, 如果用 IDE 运行一个 Maven 管理的项目, 会自动下载很多依赖到一个 .m2\repository
目录下. 目录地址在 C:\Users\%USERNAME%\.m2\repository
.
全局配置文件
安装目录 conf
文件夹下的 settings.xml
管理整个 Maven 的配置, 路径一般在 C:\Program Files\Apache\apache-maven-3.8.1\conf\settings.xml
.
用户级别配置文件
C:\Users\%USERNAME%\.m2\settings.xml
, 效果和作用与全局配置文件一样, 不过只对当前用户 %USERNAME%
生效. 而且优先级高于全局配置文件.
建议修改这个级别的配置文件.
使用代理 & 使用国内源
国内使用各种软件的时候, 最常见的问题就是网络不畅, Maven 也容易遇到这个问题. 所以我们可以设置代理或者国内源.
编辑 C:\Users\%USERNAME%\.m2
下的 settings.xml
文件. 如果没有该文件, 则从 Maven 安装目录的 conf 文件夹下拷贝一个作为 base. 比如我的在 C:\Program Files\Apache\apache-maven-3.8.1\conf
文件夹下.
使用代理
找到文件中 <proxies>XXXXX</proxies>
节点, 在里面添加相应内容:
<proxy>
<id>optional</id>
<active>true</active>
<protocol>http</protocol>
<username>proxyuser</username>
<password>proxypass</password>
<host>proxy.host.net</host>
<port>80</port>
<nonProxyHosts>local.net|some.host.com</nonProxyHosts>
</proxy>
内容根据自己的具体需要修改:
<id>
必须, 并且不能重复<host>
和<port>
必须, 毕竟没有地址和端口如何代理?<active>
必须, 且需要设置为true
, 代表启用代理.<protocol>
必须, 默认填写http
. (如果要同时支持 https 似乎需要另外再次定义一个<proxy>
节点设置为https
, 注意id
要不同)- 其他的似乎都是可选的, 比如用户名密码这些没有就不用设置了, 不要的话删掉即可.
使用国内源
找到文件中的 <mirrors>XXXXX</mirrors>
节点, 在里面填写阿里源:
<mirror>
<id>alimaven</id>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<mirrorOf>*</mirrorOf>
</mirror>
命令行操作
很多 IDE 都集成了 Maven, 所以一般只需要点点点就可以使用 Maven 完成对项目的管理. 虽然看起来比较方便, 但是不利于我们对 Maven 的理解.
不管 GUI 上怎么封装, 实际上你点点点以后, 最终都是化作几行命令行操作执行的, 所以我们还是从命令行开始进行操作练习和分析.
使用 archetype 快速开始
最简单的办法是在一个合适的文件夹下启动 cmd, 然后输入命令 archetype 命令: mvn archetype:generate
# 开始命令
> mvn archetype:generate
# 这些都是废话
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------< org.apache.maven:standalone-pom >-------------------
[INFO] Building Maven Stub Project (No POM) 1
[INFO] --------------------------------[ pom ]---------------------------------
[INFO]
[INFO] >>> maven-archetype-plugin:3.2.0:generate (default-cli) > generate-sources @ standalone-pom >>>
[INFO]
[INFO] <<< maven-archetype-plugin:3.2.0:generate (default-cli) < generate-sources @ standalone-pom <<<
[INFO]
[INFO]
[INFO] --- maven-archetype-plugin:3.2.0:generate (default-cli) @ standalone-pom ---
# 使用交互模式
[INFO] Generating project in Interactive mode
# 中间省略了几千行显示, 因为都是各种可选择的 archetype, 有兴趣的慢慢研究
# XXXXXX
# XXXXXX
# 这里开始比较重要, 如果只是使用最简单的 quick start, 那么就点击回车即可, 即使用 1769 号 archetype, 也就是 quick start
Choose a number or apply filter (format: [groupId:]artifactId, case sensitive contains): 1769:
# 回车, 默认使用 8 最新版
Choose org.apache.maven.archetypes:maven-archetype-quickstart version:
1: 1.0-alpha-1
2: 1.0-alpha-2
3: 1.0-alpha-3
4: 1.0-alpha-4
5: 1.0
6: 1.1
7: 1.3
8: 1.4
Choose a number: 8:
# 填入 groupId
Define value for property 'groupId': co.zhanglintc
# 填入 artifactId
Define value for property 'artifactId': FooBar
# 填入版本号, 一般写个 1.0 就行
Define value for property 'version' 1.0-SNAPSHOT: : 1.0
# 这里可以使用默认, package 跟 groupId 一致, 也可以改, 随意
Define value for property 'package' co.zhanglintc: :
# 回车即可, 代表确认上面的设置
Confirm properties configuration:
groupId: co.zhanglintc
artifactId: FooBar
version: 1.0
package: co.zhanglintc
Y: :
# 下面又是废话, 反正最终创建成功了
[INFO] ----------------------------------------------------------------------------
[INFO] Using following parameters for creating project from Archetype: maven-archetype-quickstart:1.4
[INFO] ----------------------------------------------------------------------------
[INFO] Parameter: groupId, Value: co.zhanglintc
[INFO] Parameter: artifactId, Value: FooBar
[INFO] Parameter: version, Value: 1.0
[INFO] Parameter: package, Value: co.zhanglintc
[INFO] Parameter: packageInPathFormat, Value: co/zhanglintc
[INFO] Parameter: package, Value: co.zhanglintc
[INFO] Parameter: groupId, Value: co.zhanglintc
[INFO] Parameter: artifactId, Value: FooBar
[INFO] Parameter: version, Value: 1.0
[INFO] Project created from Archetype in dir: C:\_Git_for_work\MVN\FooBar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 05:20 min
[INFO] Finished at: 2021-04-19T22:13:34+08:00
[INFO] ------------------------------------------------------------------------
经过以上操作, 你就在当前文件夹下等到了一个新的叫做 FooBar
的文件夹, FooBar
里面就是最简单的使用 Maven 管理的项目了.
其他命令行操作
以上面生成的 FooBar
为例, 我们进入 FooBar
目录下
mvn compile
:
C:\MVN-DEMO\FooBar
> mvn compile
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------< co.zhanglintc:FooBar >------------------------
[INFO] Building FooBar 1.0
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-resources-plugin:3.0.2:resources (default-resources) @ FooBar ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory C:\_Git_for_work\MVN\FooBar\src\main\resources
[INFO]
# 注意这里使用了 maven-compiler-plugin 插件
# compile 生命周期使用 maven-compiler-plugin 插件完成
[INFO] --- maven-compiler-plugin:3.8.0:compile (default-compile) @ FooBar ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to C:\_Git_for_work\MVN\FooBar\target\classes
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.129 s
[INFO] Finished at: 2021-04-19T22:26:13+08:00
[INFO] ------------------------------------------------------------------------
就完成了编译, 可以看到执行完后, 多出了一个 target
文件夹, 里面有很多生成的 .class
文件(但是不包含 jar 文件). 而且这里注意我们没有指定具体的 pom.xml
, 这里默认使用当前文件夹下的 pom.xml
. 命令行也可以显式的指定使用的 POM 文件, 比如 mvn compile -f C:\MVN-DEMO\FooBar\pom.xml
. 其实显示指定更好, IDE 一般也以这种形式调用. 之后我们将显式的指定.
mvn package -f C:\MVN-DEMO\FooBar\pom.xml
:
这一步完成后, 我们去 target
下观察, 会发现出现了一个 FooBar-1.0.jar
包, 这里就是 package 的效果, 完成打包.
C:\MVN-DEMO\FooBar
> mvn package -f C:\MVN-DEMO\FooBar\pom.xml
[INFO]
[INFO] -------------------------< co.zhanglintc:FooBar >--------------------------
[INFO] Building FooBar 1.0
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
# resources 生命周期, 使用 maven-resources-plugin 插件
[INFO] --- maven-resources-plugin:3.0.2:resources (default-resources) @ FooBar ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO]
# compile 生命周期, 使用 maven-compiler-plugin 插件
[INFO] --- maven-compiler-plugin:3.8.0:compile (default-compile) @ FooBar ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] --- maven-resources-plugin:3.0.2:testResources (default-testResources) @ FooBar ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO]
# test-compile 生命周期, 使用 maven-compiler-plugin:3.8.0:testCompile
[INFO] --- maven-compiler-plugin:3.8.0:testCompile (default-testCompile) @ FooBar ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 1 source file to C:\MVN-DEMO\FooBar\target\test-classes
[INFO]
# test 生命周期
[INFO] --- maven-surefire-plugin:2.22.1:test (default-test) @ FooBar ---
[INFO]
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO] Running co.zhanglintc.AppTest
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.04 s - in co.zhanglintc.AppTest
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO]
# package 生命周期
[INFO] --- maven-jar-plugin:3.0.2:jar (default-jar) @ FooBar ---
[INFO] Building jar: C:\MVN-DEMO\FooBar\target\FooBar-1.0.jar
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.891 s
[INFO] Finished at: 2021-04-30T11:19:28+08:00
[INFO] ------------------------------------------------------------------------
mvn clean -f C:\MVN-DEMO\FooBar\pom.xml
:
这一步会删除所有生成的 .class
文件和 jar
包等, 完成清理:
C:\MVN-DEMO\FooBar
> mvn clean -f C:\MVN-DEMO\FooBar\pom.xml
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------< co.zhanglintc:FooBar >------------------------
[INFO] Building FooBar 1.0
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
# 观察到这里使用了 maven-clean-plugin 插件完成清理
[INFO] --- maven-clean-plugin:3.1.0:clean (default-clean) @ FooBar ---
[INFO] Deleting C:\_Git_for_work\MVN\FooBar\target
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 0.406 s
[INFO] Finished at: 2021-04-19T22:36:28+08:00
[INFO] ------------------------------------------------------------------------
此时观察, 会发现 target 文件夹没有了, 也就是完成了清理.
以上几步是常见的一些 Maven 命令行, 其实整个 Maven 生命周期有很多个命令, 我们将在下一节进行讨论.
生命周期
程序开发的过程中, 不可避免的会经历一些阶段: 编译, 测试, 打包, 部署. 于是在 Maven 的设计中, 将这些阶段抽象出来了, 于是有了如下的生命周期:
- 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-resources
- 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
其中比较重要的一些, 我已经加粗注明了. 从生命周期的概念上来讲, 执行任何一个生命周期的操作, 都隐含的会执行该周期前面的所有操作, 于是 mvn package
就会隐含的完成 validate
, compile
, test
, package
.
不过生命周期并不是只有一个, 实际上是 3 个: clean, default, site. 任何操作只会执行自己生命周期前面的操作, 所以这里解释了为什么 mvn package
的时候, 不会隐含的完成 mvn clean
. 因为 mvn package
属于 default 周期, 而 mvn clean
属于 clean 周期.
另外一个值得注意的点是 mvn package
和 mvn install
的区别:
mvn package
完成打包, 生成的 jar 包出现在项目的target
文件夹下.mvn install
完成打包(因为隐含的完成了mvn package
), 并将该 jar 包 "install" 到 Maven 的本地仓库, 即C:\Users\%USERNAME%\.m2\repository
下. 以便本机其他项目使用.
还有一个内容就是, Maven 的生命周期是由对应的插件完成的, 即由 pom.xml 文件中指定的 <plugins></plugins>
完成的, 比如 clean
操作具体就是由 maven-clean-plugin
插件完成的, 可通过 mvn clean
等操作的输出自行观察总结. 插件可以自定义指定, 如果没有指定, Maven 也会从超级 POM 中隐含继承. 这些内容将在后续讲解.
POM 文件
pom.xml 文件是 Maven 管理用的主要文件. POM 的意思是 "Project Object Model".
一个基本的 POM.xml 文件如下, 相关描述已经写在了注释里:
<?xml version="1.0" encoding="UTF-8"?>
<!-- 标准写法, 如果是 mvn archetype:generate 生成的, 就会自动这么写好 -->
<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">
<!-- 固定写死, 目前只有 4.0.0 版本 -->
<modelVersion>4.0.0</modelVersion>
<!-- 最终名称 app-demo-1.0.jar -->
<!-- 出现在 C:\Users\%USERNAME%\.m2\repository\co\zhanglintc\app-demo-1.0.jar -->
<!-- 相当于 package 名, 会影响这个项目打成包后放在 .m2 目录的哪个路径下 -->
<groupId>co.zhanglintc</groupId>
<!-- 相当于项目名, 影响最终 jar 包的名称 -->
<artifactId>app-demo</artifactId>
<!-- 指明版本, 也会影响 jar 包的名称 -->
<version>1.0</version>
<!-- 打包方式, 一般是 pom/jar/war -->
<!-- 父项目一般是 pom, 具体项目是 jar/war, 根据需求修改 -->
<packaging>pom</packaging>
<!-- 不是必须的, 但是建议根据项目写清楚 -->
<name>app-demo</name>
<url>http://example.zhanglintc.co</url>
<!-- 定义项目中可能会用到的变量 -->
<!-- 比如 root.var 可以用 ${root.var} 的形式获取 -->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
<root.var>this-is-var</root.var>
<use.var>${root.var}</use.var>
</properties>
<!-- 如果是聚合项目, 会有此项, mvn -f 本文件会同时构建记载的两个项目 -->
<modules>
<module>Common</module>
<module>Main</module>
</modules>
<!-- 本项目的依赖写在这里 -->
<dependencies>
<!-- 有多个就写多次 <dependency> -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
</dependencies>
<!-- 想被继承的依赖写在这里 -->
<!-- 本项目不会直接使用, 主要被继承, 各子项目根据需求来使用 -->
<!-- 主要用于保证各子项目的版本号一致 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!-- build 相关操作 -->
<build>
<!-- 插件可以在之类记载, 也可以不写, 使用默认 -->
<!-- 插件与生命周期有关, 各生命周期会使用相应插件来完成操作 -->
<pluginManagement>
<plugins>
<!-- 这个插件用于 clean 生命周期 -->
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>