本文详细分析了 Maven 的全局 / 用户级配置文件 settings.xml 和项目级配置文件 pom.xml,便于查阅。
TABLE OF CONTENTS
一、工作原理
项目根据 Maven 配置,通常先从本地仓库去拉取 jar 包,如果没有则取远程仓库拉取,这个远程仓库可以是私服,也可以是中央仓库或者镜像仓库。
二、仓库管理
Maven 分为本地仓库和远程仓库,本地仓库即<localRepository>
标签配置的路径。
远程仓库分为中央仓库、私服、其他公共库。
Maven 仓库的关系:
由于墙的存在,国内访问国外中央仓库不顺利,Maven 拉取 Jar 包时会很慢甚至无法拉取,因此需要配置下国内的中央镜像仓库。镜像仓库在 Maven 配置文件 settings.xml 下进行配置。
镜像仓库:将国外的中心仓库复制一份到国内,提升访问速度。
私服:基于系统保密性的原因,搭建的内部远程仓库,存储一些公司内部不希望被公开的依赖服务,也可以避免因外部中央仓库网络的访问不顺畅导致的各种问题。
三、Maven 配置文件
Maven 配置文件有三种:
- 全局配置文件:
%MAVEN_HOME%\conf\settings.xml
- 用户配置文件:
C:\Users\用户名\.m2\settings.xml
- 项目配置文件:项目下的
pom.xml
配置优先级:项目 pom.xml > 本地 settings > 全局 settings,如果配置文件同时存在,会进行合并,有重复的配置时,优先级高的配置会覆盖优先级低的配置。
四、Settings 配置详解
首先,可以从 Maven 安装目录的 conf 下查看配置文件 settings.xml,示例如下(注意仅展示结构,要经过定制才可使用):
1 |
|
1、localRepository
配置本地仓库位置,默认位置为${user.home}/.m2/repository
,可以配置为
1 | <localRepository>D:\Workslace\Maven\maven-repository</localRepository> |
2、interactiveMode
Maven 是否需要和用户交互以获得输入,默认为 true
3、offline
用来标识是否以离线模式运营 Maven。当系统不能联网时,可以通过该配置来离线运行。
4、pluginGroups
pluginGroups 当插件的组织 id(groupId) 没有显示提供时,供搜寻插件组织 Id 的列表。
5、servers
访问私服时,某些私服需要配置认证信息,在这里填写。之所以 servers 配置不写在 pom.xml,是因为 pom.xml 会随着代码上传代码仓库,而 settings.xml 存储在本地,相对安全。因此,通常在 settings.xml 中配置 servers
1 | <servers> |
6、proxies
用来配置代理。
1 | <proxies> |
7、mirrors
配置镜像仓库。
1 | <mirrors> |
当本地仓库没有需要的依赖时,根据<mirrorOf>
匹配远程仓库请求,去对应的镜像仓库地址拉取依赖。
下面是一些常用的语法示例:
<mirrorOf>*<mirrorOf>
:匹配所有远程仓库。<mirrorOf>external:*<mirrorOf>
:匹配所有不在本机上的远程仓库。<mirrorOf>repo1,repo2<mirrorOf>
:匹配仓库 repo1 和 repo2,使用逗号分隔多个远程仓库。<mirrorOf>*,!repo1<mirrorOf>
:匹配所有远程仓库,repo1 除外,使用感叹号将仓库从匹配中排除。
注意:
镜像仓库完全屏蔽了被镜像仓库,当镜像仓库不稳定或者停止服务的时候,Maven 仍将无法访问被镜像仓库,因而将无法下载 jar 包。
此外,maven 读取 mirror 配置是从上往下读取的,因此谨慎配置<mirrorOf>*<mirrorOf>
,因为如果第一个镜像仓库配置了如此标志,那么如果该仓库即使不存在对应依赖也不会向下游查询。
8、profiles
根据环境参数来调整构建配置的列表。可以将 settings.xml 下的 profile 理解为 pom.xml 种 profile 的阉割版。
settings.xml 中的 profiles 包含了 id、activation、repositories、pluginRepositories 和 properties 元素
注意:
如果一个 settings.xml 的 profile 被激活,那么它的值会覆盖任何其他定义在 pom.xml 种带有相同 id 的 profile
激活 profile 有三种方式
- 通过命令行编译构建时激活,最常用的方式。
- 通过 activeProfiles 直接激活,详细往下看。
- 通过 activation 激活,详细往下看。
通过命令行激活
通常在 pom.xml 中定义不同环境的 profile,在编译构建不同的包时,执行下面的命令即可激活对应的 profile 进行编译构建。
以开发环境为例:
1 | 命令格式:mvn package -P [profile的ID] |
repositories
定义了一组远程仓库的列表,当该属性对应的 profile 被激活时,会使用该远程仓库。
1 | <repositories> |
properties
定义一组拓展属性,当对应的 profile 被激活时该属性才生效。
执行mvn help:system
可以获取 System Properties 和 Environment Variables,根据下面注释进行使用即可。
1 | <!-- |
id
全局唯一标识,如果一个 settings.xml 中的 profile 被激活,它的值会覆盖任何其它定义在 pom.xml 中带有相同 id 的 profile。
pluginRepositories
同 repositories 差不多,不过该标签定义的是插件的远程仓库。
activation
触发激活该 profile 的条件。注意在 Maven 3.2.2 之前,当满足一项指定条件时就会激活。从 Maven 3.2.2 开始,需要满足所有指定条件时才会激活。可以移步官方文档查阅。
Before Maven 3.2.2 activation occurs when one or more of the specified criteria have been met. When the first positive result is encountered, processing stops and the profile is marked as active. Since Maven 3.2.2 activation occurs when all of the specified criteria have been met.
1 | <activation> |
9、activeProfiles
指定激活的 profile,不管 profile 的 activation 配置如何,都会激活对应的 profile。
1 | <activeProfiles> |
10、Settings.xml 配置文件中mirrors
和profile中repositories
的关系
两个标签都定义了一个远程仓库的位置,如果一个依赖同时存在于这 2 个仓库,会先加载哪个依赖?
mirrors 相当于拦截器,maven 加载依赖时,如果 mirror 的 mirrorOf 配置的值与对应的 repository 的 id 相同,那么 mirror 的仓库地址替换掉 repository 的仓库地址。
五、pom.xml 配置详解
settings.xml 定义的是全局或用户的配置,pom.xml 定义的是一个项目的依赖配置。
1 | <project xmlns = "http://maven.apache.org/POM/4.0.0" |
1、dependencies
定义了项目中所需要的相关依赖。
1 | <dependencies> |
2、dependencyManagement
一个服务中存在多个 module 时,每个子 module 可能都引用了相同的 jar 包,此时可以通过 maven 继承,父级 pom.xml 统一管理依赖版本。需要使用到<dependencyManagement>
标签。
示例:
父级 pom.xml
1 | <!--在父pom中定义子pom需要的相关依赖 --> |
子 module 的 pom.xml
1 | <!--在子pom中 如下定义了父pom中相关依赖信息 --> |
3、properties
properties 主要用来定义常量,常见的有依赖版本、JDK 版本、字节编码等,通过 ${value} 来使用。
1 | <!--配置依赖版本--> |
此外,Maven 还通过约定大于配置的方式定义了一些常用的属性:
属性 | 定义 |
---|---|
${basedir} |
存放 pom.xml 和所有的子目录 |
${basedir}/src/main/java |
项目的 java 源代码 |
${basedir}/src/main/resources |
项目的资源,比如说 property 文件,springmvc.xml |
${basedir}/src/main/webapp/WEB-INF |
web 应用文件目录,web 项目的信息,比如 web.xml |
${basedir}/target |
打包输出目录 |
${project.version} |
项目版本 |
${project.groupId} |
项目 groupId |
4、resources
<build>
下面的<resources>
标签用来标识项目在编译运行时需要额外编译的文件。例如手工引入 jar 包、不同运行环境对应不同的 profile 等等。
<testResources>
用法与<resources>
类似。
1 | <build> |
5、profile
通常地,setting.xml 用来标识不同的远程仓库,而 pom 中的 profile 一般用来标识当前 profile 的配置属于哪个环境,当然也可以用来指定远程仓库。
在编译打包时,通过mvn package -P prod
命令来激活不同环境。
1 | <profiles> |
在 Intellij IDEA 的 Maven 窗口中,profile 作为一个个的可选项存在。
6、modules
项目中存在多个 module 时,如果需要单独打包需要在每一个 module 都执行 maven 命令,通过父级 pom.xml 添加的<modules>
标签可以将自服务进行聚合,只需要打包该服务,也会将子 module 同时打包。
1 | <modules> |
7、查找并添加依赖
配置好 Maven 后,想要添加什么依赖,可以到仓库索引搜索,然后选择对应的坐标,复制到 pom.xml 文件<dependencies>
中即可。
8、依赖范围管理
Maven 项目构建生命周期包括 clean 清理项目,default 构建项目,site 生成项目文档和站点。重点关注 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 | 测试 | 使用合适的测试框架运行测试(一般我们在 idea 种开发项目时,可以选择屏蔽掉 test 步骤)。 |
prepare-package | 准备打包 | 进行必要的操作,以便进行打包。 |
package | 打包 | 将编译后的代码打包成可分发的格式,例如 JAR、WAR。 |
pre-integration-test | 集成测试前 | 在集成测试之前进行的操作。 |
integration-test | 集成测试 | 处理和部署项目,以便进行集成测试。 |
post-integration-test | 集成测试后 | 在集成测试之后进行的操作。 |
verify | 验证 | 检查包是否有效,符合质量标准。 |
install | 安装 | 将包安装到本地仓库,以便其他项目依赖。 |
deploy | 部署 | 将最终的包复制到远程仓库,共享给其他开发人员和项目。 |
在 pom.xml 文件的<dependency>
标签中有一个子标签<scope>
,默认值为 compile,记录了依赖的 jar 包在哪些环境有效
Scope | 编译环境 | 测试环境 | 运行环境 | 例子 | 描述 |
---|---|---|---|---|---|
compile | Y | Y | Y | spring-core | 依赖对所有的 classpath 都有效 |
provided | Y | Y | - | servlet-api | 执行打包mvn package 时会移除,运行时由应用服务器提供 |
system | Y | Y | - | 本地的,Maven 仓库之外的类库文件 | 类似 provided,但依赖项不会从 maven 仓库中查找,通过<systemPath> 标签获取 |
runtime | - | Y | Y | jdbc 驱动包 | 编译的时候不需要,只在运行和测试的时候需要用到 |
test | - | Y | - | Junit/Mockito | 只在测试编译和测试运行阶段可用 |
还有一个特殊的取值:import
,仅在父级 pom.xml 的<dependencyManagement>
的标签中可用,不做展开。
scope 为 system 示例:
1 | <dependency> |
关于 Dependency Scope 的官方描述,请查阅:https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html
总结
通过 maven 引入的 jar 包,里面的类都是已经编译好的字节码,与 scope 参数没有什么联系。
compile、runtime 和 provided 的区别,需要在执行打包mvn package
命令,并且打包格式时 war 时才能看出来。
通过 compile 和 runtime 引入的 jar 包,执行打包命令后会出现在 war 包里,而 provided 引入的 jar 包则不会。
通过 compile 和 provided 引入的 jar 包,里面的类,在项目中直接 import 进来就可以使用了,编译也没有问题;
而通过 runtime 引入的 jar 包中的类,项目代码不能直接使用,用了无法通过编译,只能通过反射的方式来调用。
9、解决依赖冲突
依赖传递遵循三个原则:
- 最短路径优先:项目中存在两级以上的不同依赖,引用同一个依赖时,层级越浅,优先级越高。举例:项目 A -> B -> C1,A -> D -> E -> C2,其中 C1 和 C2 是同一个依赖的不同版本,此时 A 项目以 C1 的版本为准。
- 声明优先:对于两级以上的同级依赖,先声明的依赖会覆盖后声明的依赖包。举例:项目 A -> B -> C1,A -> D -> C2,以 C1 版本为准。
- 特殊优先:同级依赖中,后加载覆盖先加载原则。举例:项目 A->C1,再次配置 A->C2,那么以 C1 版本为准。
Intellij IDEA 解决依赖冲突思路:
- 通过 Maven Helper 插件的 Dependency Analyzer 对 pom 文件进行依赖分析,找到冲突或重复的 jar 包,用
<exclusions>
标签排除不需要的版本,要注意是否存在兼容性问题。此外也可以通过命令行mvn dependency:tree
查看项目的依赖树,观察实际加载的版本。 - 编译打包后打开查看实际打的 jar 包版本,进行核对
依赖分析相关的 maven 命令
mvn dependency:list
:查看项目已解析的依赖。mvn dependency:tree
查看项目的依赖树。mvn dependency:analyze
:分析项目的依赖信息,有 2 种,根据需要选择主动声明,或者移除不需要的依赖。
- Used undeclared dependencies,项目中已使用但未声明的依赖。
- Unused declared dependencies,项目中未使用但已声明的依赖。
六、参考资料
Maven 进阶学习指南 | 京东云技术团队
实际上手体验 maven 面对冲突 Jar 包的加载规则 | 京东云技术团队
声明:本站所有文章均为原创或翻译,遵循署名 - 非商业性使用 - 禁止演绎 4.0 国际许可协议,如需转载请确保您对该协议有足够了解,并附上作者名 (Tsukasa) 及原文地址