Maven是一个项目管理工具,它包含了项目对象模型(POM:Project Object Model),项目生命周期(Project Lifecycle),依赖管理系统(Dependency Management System),和用来运行定义在生命周期阶段(phase)的插件(plugin)以及一组标准集合。
Maven的两个核心作用
gav,项目中依赖的第三方库以及插件可统称为构件(也可以叫依赖)。每一个构件都可以使用 Maven 坐标唯一标识,也就是说通过 Maven 坐标,我们就可以找到对应的唯一的构件,坐标元素包括:
本地仓库:settings.xml 文件中可以看到 Maven 的本地仓库路径配置,默认本地仓库路径是在 ${user.home}/.m2/repository。
远程仓库:官方或者其他组织维护的 Maven 仓库,远程仓库可以分为三种:
Maven 依赖包寻找顺序:先本地仓库 -> 后远程仓库 -> 都没有则报错
dependencies:一个 pom.xml 文件中只能存在一个这样的标签,是用来管理依赖的总标签。
dependency:包含在 dependencies 标签中,可以有多个,每一个表示项目的一个依赖。
groupId,artifactId,version(必要):依赖的基本坐标,对于任何一个依赖来说,基本坐标是最重要的,Maven 根据坐标才能找到需要的依赖。
type(可选):依赖的类型,对应于项目坐标定义的 packaging。大部分情况下,该元素不必声明,其默认值是 jar。
scope(可选):依赖的作用范围,默认值是 compile。
optional(可选):标记依赖是否可选
exclusions(可选):用来排除传递性依赖,例如 jar 包冲突
classpath 用于指定 .class
文件存放的位置,类加载器会从该路径中加载所需的 .class
文件到内存中。
Maven 编译、测试、运行有三套不同的 classpath:
Maven 的依赖范围如下:
1、对于 Maven 而言,同一个 groupId 同一个 artifactId 下,只能使用一个 version。若相同类型但版本不同的依赖存在于同一个 pom 文件,只会引入后一个声明的依赖。
2、由于 Maven 的依赖传递,如果项目的两个依赖同时引入了某个版本不一致的依赖,则会产生冲突
两个原则:最短路径优先 和 声明顺序优先
假设 A->B->C->X(2.0),A->D->X(1.0),则 X(1.0) 会被引用。
假设 A->B->C->X(1.0),A->D->C->X(2.0),如果 B 声明在 D 之前,则X(1.0)被引用,反之。
此时可通过依赖分析,找到有冲突的依赖,比如使用 IDEA插件 Maven Helper 等
如果只依靠 Maven 进行依赖调解,很多情况下是不能满足的,需要手动排除依赖。
还是 A->B->C->X(2.0),A->D->X(1.0) 这个例子,根据最短路径优先原则,我们引入了 X(1.0),但是如果 C 用到了某个 X(1.0) 没有的类或者方法,就会报找不到类/方法的错误(NoSuchMethodError 和 ClassNotFoundException)。
此时,就需要我们通过 exclusion 标签将 X(1.0) 给排除,这样 Maven 就会引用 X(2.0)。
在解决依赖冲突的时候,一般会优先保留版本较高的。这是因为大部分 jar 在升级的时候都会做到向下兼容。
Maven 标准生命周期 | 作用 |
---|---|
clean | 项目清理(删除 target目录) |
default(build) | 项目部署 |
site | 项目站点文档创建 |
我们平时接触最多的就是 default 生命周期,并且七个核心阶段都是顺序执行的。
核心阶段 | 作用 |
---|---|
validate | 验证项目是否正确,所有必要信息是否可用(很少单独使用) |
compile | 编译项目的源代码(将src/main中的java代码编译成class文件,输出到targe目录下) |
test | 将单元测试的资源文件和代码进行编译,生成的文件位于target/test-classes (打包部署请跳过该阶段) |
package | 把class文件,resources文件打包成jar包(也可以是war包),生成的jar包位于target目录下 |
verify | 检查包是否有效(很少单独使用) |
install | 将jar部署到本地仓库,本地的其他模块依赖该jar包时,可以直接从本地仓库去获取 |
deploy | 将jar包部署到远端仓库,需要在maven的setting.xml中配置私服的用户名和密码,以及在pom.xml配置 |
也就是说,如果我们执行了 mvn package,会从 mvn validate 开始一直执行到 package,其他同理。但是不会执行 mvn clean,因为 clean、site、default 都是独立的生命周期。因此,保险起见,在执行 package、install、deploy 之前先 clean。
聚合:对于复杂的Maven项目,一般建议采用多模块的方式来设计开发,便于后期维护管理。但是构建项目时,如果每次都需要按模块一个一个进行构建会十分麻烦,而Maven的聚合功能就可以很好的解决这个问题,当用户对聚合模块执行构建任务时,会对所有被其聚合的模块自动地依次进行构建任务。
继承:在一个多模块的项目中,对于同一个依赖的依赖声明要在多个模块的POM都进行声明,会导致有大量重复的依赖声明。所以Maven在设计之时,借鉴了面向对象中的继承思想,可在父模块的POM中声明依赖,子模块的POM文件可通过继承父模块的POM来获得对相关依赖的声明。对于父模块而言,其目的是为了消除子模块的POM文件的重复配置,其不含有任何实际的项目代码,所以父模块POM文件的packaging元素同样需要设置为pom。
参考文章&推荐阅读
Maven – 构建生命周期简介 (apache.org)
高效使用Java构建工具|Maven篇 (qq.com)
maven学习笔记(超详细总结) - clear_love8 - 博客园 (cnblogs.com)