Maven简介
Maven 是一个项目管理和整合工具。Maven 包含了一个项目对象模型 (Project Object Model),一组标准集合,一个项目生命周期管理系统(Project Lifecycle Management System),一个依赖管理系统(Dependency Management System),和用来运行定义在生命周期阶段(phase)中插件(plugin)目标(goal)的逻辑。
Maven 为开发者提供了一套完整的构建生命周期框架。开发团队几乎不用花多少时间就能够自动完成工程的基础构建配置,因为 Maven 使用了一个标准的目录结构和一个默认的构建生命周期。
Maven 优缺点
Maven优点如下:
(1) 简化了项目依赖管理;
(2) 易于上手,对于新手可能一个"mvn clean package"命令就可能满足日常的工作;
(3) 便于与持续集成工具(jenkins)整合;
(4) 便于项目升级,无论是项目本身升级还是项目使用的依赖升级;
(5) 有助于多模块项目的开发,一个模块开发好后,发布到仓库,依赖该模块时可以直接从仓库更新,而不用自己去编译;
(6) Maven有很多插件,便于功能扩展,比如生产站点,自动发布版本等。
但Maven也有以下缺点:
(1) Maven是一个庞大的构建系统,学习难度大;
(2) Maven采用约定优于配置的策略(convention over configuration),虽然上手容易,但是一旦出了问题,难于调试。
Ant、Maven、Gradle 对比
(1) Ant的低学习成本、通用性、灵活性,使得它仍然在一些老牌框架或者项目中被使用到,但是Ant没落是一个不争的事实。由于Ant的灵活性,导致可能会出现难以维护的巨大XML构建文件。另外,起初由于Ant没有内置依赖管理支持。但是,由于依赖管理在以后的几年中成为必需,Apache Ivy被开发为Apache Ant项目的子项目。它与Apache Ant集成,遵循相同的设计原则。但是,由于在使用不可管理的XML构建文件时,没有内置支持依赖关系管理,导致Ant没有崛起,反倒导致限制了Maven的创建和流行。
(2) Maven是一个依赖项管理和构建自动化工具,主要用于Java应用程序。Maven继续像Ant一样使用XML文件,但是更易于管理。虽然Ant提供了灵活性并且需要从头开始编写所有内容,但Maven依赖于约定并提供预定义的命令,降低了使用门槛。此外,Maven对依赖管理提供了内置支持。Maven还规定了严格的项目结构,而Ant也提供了灵活性。Maven变得非常受欢迎,因为构建文件现在已经标准化,与Ant相比,维护构建文件的时间要少得多。
(3) Gradle是依赖关系管理和构建自动化工具,它基于Ant和Maven的概念。尽管Maven在使应用程序的构建过程更容易和更标准化方面取得了一些重大改进,但由于其灵活性远低于Ant,因此仍然需要付出代价。这导致了Gradle的创造,它结合了两者的优点–Ant的灵活性和Maven的功能。
总结:
在一些老牌框架或者项目中,Ant因其低学习成本、通用性、灵活性仍被使用,但是Ant已逐渐退出历史舞台,不建议使用。
Maven目前占据了当今构建工具市场的大部分,这主要归功于Maven的:约定优于配置的理念(使其使用门槛大大降低)、提供了依赖管理的能力、严格的项目结构(标准化)等特性。
Gradle在更复杂的代码库中得到了很好的采用。相比Ant和Maven,Gradle吸收了两者的优点:吸收了Ant的灵活性(使用领域语言DSL定义构建脚本、多模块构建),沿用了Maven的依赖管理体系,但更简洁,Gradle也沿用了Maven标准的目录结构,但可以更优雅的进行配置。(如果在Gradle项目中使用了标准的Maven项目结构的话,那么在Gradle中也无需进行多余的配置,只需在文件中包含apply plugin:’java’,系统会自动识别source、resource、test srouce、 test resource等相应资源。另外,Gradle作为JVM上的构建工具,也同时支持groovy、scala等源代码的构建,甚至支持Java、groovy、scala语言的混合构建)。
此外,相比Maven 的 phase都是串行的,整个执行下来是一条线,Gradle在构建模型上则非常灵活。在Gradle世界里可以轻松创建一个task,并随时通过depends语法建立与已有task的依赖关系。甚至对于Java项目的构建来说,Gradle是通过名为java的插件来包含了一个对Java项目的构建周期,这等于Gradle本身直接与项目构建周期是解耦的。
简言之,新项目不建议使用Ant构建、推荐选择Maven或Gradle。如果是新增大型项目,推荐使用Gradle。Gradle有替代Maven的趋势,推荐对其做相关的学习。
Maven生命周期管理
一个构建生命周期是一组精心组织的有序的阶段,它的存在能使所有注册的目标变得 有序运行。这些目标根据项目的打包类型被选择并绑定。Maven中有三种标准的生命周期:清理(clean),默认(default)(有时候也称为构建),和站点(site)。
清理生命周期
运行 “mvn clean” 将调用清理生命周期,它包含三个生命周期阶段:pre-clean、clean、post-clean。
在清理生命周期中最有意思的阶段是clean阶段。Clean插件的clean目标 (clean:clean)被绑定到清理生命周期中的clean阶段。目标clean:clean通过删除构建目录删除整个构建的输出。
当运行clean:clean目标的时候,并不是直接运行"mvn clean:clean"命令,而是通过执行清理生命周期的clean阶段运行该目标。
默认生命周期
大部分Maven用户最常用的是默认生命周期,它是一个软件应用构建过程的总体模型。Maven默认生命周期包含如下阶段:
Process Resources
大部分生命周期将resources:resources目标绑定到process-resources阶段。process-resources阶段处理资源并将资源复制到输出目录。默认情况下,Maven就会将src/main/resources中的文件复制到target/classes目录。除了复制资源文件至输出目录,Maven同时也会在资源上应用过滤器,能让开发者替换资源文件中的一些符号。与profile联系起来,这样的特性就能用来生成针对不同部署平台的构件。
Compile
大部分生命周期将Compiler插件的compile目标绑定到compile阶段。该阶段会调用compile:compile,后者被配置成编译所有的源码并复制到构建输出目录。默认情况下,compile:compile会将编译src/main/java中的所有内容至target/classes。
注意,Compiler插件调用javac,使用的source设置为1.3,默认target设置为1.1。换句话说,Compiler插件会假设所有的Java源代码遵循Java 1.3,目标为 Java 1.1 JVM。如果想要更改这些设置,就需要在POM中为Compiler插件提供source和target配置。示例如下:
<project> ...<build> ...<plugins> <plugin><artifactId>maven-compiler-plugin</artifactId> <configuration><source>1.8</source><target>1.8</target></configuration></plugin></plugins>...</build>...
</project>
Process Test Resources
process-test-resources阶段最难和process-resources阶段区别。在POM中它们有一些微小的差别,但大部分是一样的。开发者可以像过滤一般的资源那样过滤测试资源。
测试资源的默认位置定义在超级POM中,为src/test/resources,默认的输出目录为target/test-classes。
Test Compile
test-compile阶段基本上和compile阶段一致。唯一的不同是会调用compile:testCompile编译测试源代码目录至测试构建构建输出目录。默认情况下,compile:testCompile将会编译src/test/java中的源码 至target/test-classes目录。
Test
大部分生命周期绑定Surefire插件的test目标至test阶段。Surefire插件是Maven的单元测试插件,Surefire默认的行为是寻找测试源码目录下所有以*Test结尾的类,以JUnit2测试的形式运行它们。Surefire插件也可以配置成运行TestNG3单元测试。
运行过mvn test之后,注意到Surefire在target/surefire-reports目录生成了许多报告。该目录内每个Surefire插件运行过的测试都会有相关的两个文件:一个是包含测试运行信息的XML文档,另一个是包含单元测试输出的文本文件。如果测试阶段有问题,单元测试失败了,可以使用Maven的输出以及该目录下的内容来追查测试失败的原因。
如果工作的项目有一些失败的单元测试,同时想让项目生成输出,需要配置Surefire插件在遇到失败的情况下继续一个构建。当遇到单元测试失败的时候,默认行为是停止构建。要覆盖这种行为,需要设置Surefire插件的testFailureIgnore配置属性为true。
<build> <plugins><plugin><groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId><configuration><testFailureIgnore>true</testFailureIgnore></configuration></plugin>...</plugins>...
</build>
如果想跳过整个测试阶段,可以运行如下的命令:
mvn install -Dmaven.test.skip=true
Install
Install插件的install目标基本上都是绑定到install生命周期阶段。install:install目标只不过是将项目的主要构件安装到本地仓库。如果打包类型是JAR,那么install:install就会将target目录下的jar包拷贝到本地仓库。如果打包类型是POM,那么install:install就仅仅是复制POM到本地仓库。
Deploy
Deploy插件的deploy目标通常绑定到deploy生命周期阶段。该阶段用来将一个构件部署到远程Maven仓库,当执行一次发布的时候通常需要更新远程仓库。一次部署过程可以简单到复制一个文件至另外一个目录,或者复杂到使用公钥通过SCP传送一个文件。部署设置通常包含远程仓库的证书,并且,这样的部署设置通常不会存储在pom.xml中。部署设置通常可以在用户单独的~/.m2/settings.xml中找到。
简单来说,deploy:deploy目标是传送一个构件至发布仓库,更新一些可能被此次部署影响的仓库信息。
站点生命周期
Maven不仅仅能从一个项目构建软件构件,它还能为一个或者一组项目生成项目文档和 报告。项目文档和站点生成有一个专有的生命周期,它包含了四个阶段: pre-site、site、post-site、site-deploy。
默认绑定到站点生命周期的目标是: site-site:site 和 site-deploy-site:deploy。
打包相关生命周期
绑定到每个阶段的特定目标默认根据项目的打包类型设置。一个打包类型为jar的项目和一个打包类型为war的项目拥有不同的两组默认目标。packaging元素影响构建一个项目需要的步骤。举个打包如何影响构建的例子,考虑有两个项目:一个打包类型 是pom,另外一个是jar。在package阶段,打包类型为pom的项目会运行site:attach-descriptor目标,而打包类型为jar的项目会运行jar:jar目标。
JAR
JAR是默认的打包类型,是最常用的,也是生命周期配置中最经常遇到的打包类型。JAR生命周期默认的目标如下所示:
POM
POM是最简单的打包类型。它生成的构件只是它本身。 没有代码需要测试或者编译,也没有资源需要处理。POM生命周期默认的目标如下所示:
其他类型
除了JAR、POM,还有其他打包类型,如Maven Plugin、EJB、WAR、EAR等。此外,Maven也支持自定义打包类型,定制默认的生命周期目标来适应项目的打包需求。
Maven依赖管理
当依赖的范围是 system 的时候,Maven 直接从本地文件系统中解析构件。
根据依赖坐标计算仓库路径,尝试直接从本地仓库寻找构件,如果发现对应的构件,就解析成功。如果在本地仓库不存在相应的构件,就遍历所有的远程仓库,发现后,下载并解析使用。
如果依赖的版本是 RELEASE 或 LATEST,就基于更新策略读取所有远程仓库的元数据文件(groupId/artifactId/maven-metadata.xml),将其与本地仓库的对应元合并后,计算出RELEASE 或者 LATEST 真实的值,然后基于该值检查本地仓库,或者从远程仓库下载。
如果依赖的版本是 SNAPSHOT,就基于更新策略读取所有远程仓库的元数据文件,将它与本地仓库对应的元数据合并,得到最新快照版本的值,然后根据该值检查本地仓库,或从远程仓库下载。如果最后解析得到的构件版本包含有时间戳,先将该文件下载下来,再将文件名中时间戳信息删除,剩下 SNAPSHOT 并使用(以非时间戳的形式使用)。
Maven 依赖原则
(1)最短路径优先原则。
一个项目 Demo 依赖了两个 jar 包,其中 A-B-C-X(1.0) , A-D-X(2.0)。由于 X(2.0) 路径最短,所以项目使用的是 X(2.0)。
(2) pom文件中先声明先使用原则。
如果 A-B-X(1.0),A-C-X(2.0)这样的路径长度一样怎么办呢?这样的情况下,Maven 会根据 pom 文件声明的顺序加载,如果先声明了 B ,后声明了 C ,那依赖就会是X(1.0)。
(3) 子类覆写原则。
子 pom 内声明的优先于父 pom 中的依赖。
依赖范围
compile(编译范围) compile是默认的范围;如果没有提供一个范围,那该依赖的范围就是编译范围。编译范围依赖在所有的classpath中可用,同时它们也会被打包。
provided(已提供范围) provided依赖只有在当JDK或者一个容器已提供该依赖之后才使用。例如,如果 你开发了一个web应用,你可能在编译classpath中需要可用的Servlet API来编译一个servlet,但是你不会想要在打包好的WAR中包含这个Servlet API;这个 Servlet API JAR由你的应用服务器或者servlet容器提供。已提供范围的依赖在编译classpath(不是运行时)可用。它们不是传递性的,也不会被打包。
runtime(运行时范围) runtime依赖在运行和测试系统的时候需要,但在编译的时候不需要。比如,可能在编译的时候只需要JDBC API JAR,而只有在运行的时候才需要JDBC驱动实现。
test(测试范围)
test范围依赖 在一般的 编译和运行时都不需要,它们只有在测试编译和测试运行阶段可用。
system(系统范围)
system范围依赖与provided类似,但是你必须显式的提供一个对于本地系统中 JAR文件的路径。这么做是为了允许基于本地对象编译,而这些对象是系统类库 的一部分。这样的构件应该是一直可用的,Maven也不会在仓库中去寻找它。如 果你将一个依赖范围设置成系统范围,你必须同时提供一个systemPath元素。注 意该范围是不推荐使用的(你应该一直尽量去从公共或定制的Maven仓库中引用 依赖)。
依赖冲突
遇到冲突的时候第一步,要找到 Maven 加载的到时是什么版本的 jar 包,通过们 mvn dependency:tree 查看依赖树,或者使用 IDEA Maven Helper 插件。
然后,通过 Maven 的依赖原则来调整坐标在 pom 文件的申明顺序是最好的办法,或者通过dependency的exclusion元素排除掉依赖。
Maven 插件机制
插件也是基于坐标保存在Maven仓库中(与依赖类似)。
在用到插件的时候会先从本地仓库查找插件,如果本地仓库没有则从远程仓库查找插件并下载到本地仓库。
与普通的依赖构件不同的是,Maven会区别对待普通依赖的远程仓库与插件的远程仓库。当Maven需要的插件在本地仓库不存在时,是不会去配置的远程仓库查找插件的,而是需要有专门的插件远程仓库。
Maven 使用
如何使用Maven实现多模块构建?
多模块项目由管理一组子模块的聚合器 POM 构建。在大多数情况下,聚合器位于项目的根目录中,并且必须具有POM类型的打包。
子模块是常规的 Maven 项目,它们可以单独构建,也可以通过聚合器 POM 构建。
通过聚合器 POM 构建项目,每个具有与pom不同的打包类型的项目都会生成一个构建的存档文件。
使用Maven 经常遇到的问题有哪些怎么解决的?
(1) 可能由于网络波动,包没有下载完成,但是又缓存了,这个时候下载多少次都会失败,解决办法就是去本地仓库将对应的依赖全部删除再重新下载
(2) 由于依赖传递带来的版本冲突问题,解决办法就是排除依赖。
(3) 本地依赖(调试阶段)
如何使用Maven适配多种场景的构建
使用Maven Profile插件可以实现多种场景的适配。
Profile能为一个特殊的环境自定义一个特殊的构建;profile使得不同环境间构建的可移植性成为可能。
Maven中的profile是一组可选的配置,可以用来设置或者覆盖配置默认值。有了 profile,就可以为不同的环境定制构建。profile可以在pom.xml中配置,并给定一个id。然后就可以在运行Maven的时候使用的命令行标记告诉Maven运行特定profile中的目标。以下pom.xml使用production profile覆盖了默认的Compiler插件设置。
<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">...<profiles><profile><id>production</id><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> </plugin></plugins></build></profile></profiles>
</project>
本例中,添加了一个名为production的profile,它覆盖了Maven Compiler插件的默认配置。
要使用production profile来运行mvn install,需要在命令行传入-P production参数。
mvn install -P production
虽然前面的样例展示了如何覆盖一个Maven插件的默认配置属性,但仍然没有确切知道Maven profile能覆盖什么。简单的回答这个问题,Maven profile可以覆盖几乎所有pom.xml中的配置。Maven POM包含一个名为profiles的元素,它包含了项目的替代配置,在这个元素下面,每个profile元素定义了一个单独的profile。每个profile必须要有一个id,除此之外,它可以包含几乎所有你能在project下看到的元素。以下的XML文档展示了一个profile允许覆盖的所有的元素。
<project><profiles><profile><build><defaultGoal>...</defaultGoal><finalName>...</finalName><resources>...</resources><testResources>...</testResources><plugins>...</plugins></build><reporting>...</reporting><modules>...</modules><dependencies>...</dependencies><dependencyManagement>...</dependencyManagement> <distributionManagement>...</distributionManagement><repositories>...</repositories><pluginRepositories>...</pluginRepositories><properties>...</properties></profile></profiles>
</project>
参考
https://zhuanlan.zhihu.com/p/163809219 《maven权威指南》读书笔记
https://github.com/camark/mvnbook maven权威指南翻译版(未见正式书籍)
https://www.dba.cn/book/maven/ Maven中文手册
https://blog.csdn.net/happydecai/article/details/80492212 Maven常见面试题
https://www.cnblogs.com/ISAN-Liu/p/6688106.html Java 中三大构建工具:Ant、Maven和Gradle
https://www.baeldung.com/ant-maven-gradle Ant vs Maven vs Gradle
https://blog.csdn.net/nalw2012/article/details/92802062 三大构建工具比较——Ant vs Maven vs Gradle
https://www.jrebel.com/blog/java-build-tools-comparison Ant vs Maven vs Gradle: Java Build Tools Comparison
https://blog.csdn.net/qq_27706119/article/details/118342855 Maven默认的生命周期
https://zhuanlan.zhihu.com/p/64475369 Maven常见面试题
https://www.cnblogs.com/lin0/p/14153982.html Maven笔记之面试题合集
http://www.bjpowernode.com/toolmst/mavenmst.html Maven面试题
https://blog.csdn.net/weixin_39570751/article/details/127624384 Maven面试题
https://blog.csdn.net/sinat_26552841/article/details/128697508 maven常见面试题
https://blog.csdn.net/allway2/article/details/124840866 使用 Maven 构建多模块项目