前言
当我们在开发项目时,有时需要用到外部依赖组件,例如当我们需要 Json 序列化的时候需要用到 FastJson 组件,我们可以通过下载对应 jar 包加载到项目中。但当一个大的项目同时需要依赖各种各样的外部服务,就存在着配置繁琐、依赖冲突等问题,因此可以通过 maven 来完成对应的依赖管理功能
一、Settings 配置
settings.xml 用来配置 maven 项目中的各种参数文件,包括本地仓库、远程仓库、私服、认证等信息。
1.1 配置概述
1.1.1 全局 settings、用户 setting、pom 的区别
- 全局 settings.xml 是 maven 的全局配置文件,一般位于 ${maven.home}/conf/settings.xml,即 maven 文件夹下的 conf 中。
- 用户 setting 是 maven 的用户配置文件,一般位于 ${user.home}/.m2/settings.xml,即每位用户都有一份配置文件。
- pom.xml 文件是项目配置文件,一般位于项目根目录下或子目录下。
配置优先级从高到低:pom.xml > 本地 settings > 全局 settings
如果这些文件同时存在,在应用配置时,会合并它们的内容,如果有重复的配置,优先级高的配置会覆盖优先级低的。
1.1.2 仓库【重要】
如前言所述,我们依赖的外部服务是需要有地方进行存储的,而存储的地方就称之为仓库。其中仓库又分为本地仓库、中央仓库、镜像仓库、私服。
其对应关系为:
(1)本地仓库
当项目在本地编译或运行时,直接加载本地的依赖服务无疑是最快的。默认情况下,不管在 Window 还是 Linux 下,每个用户在自己用户目录下都有一个路径名为.m2/repository/ 的仓库目录。
而原始的本地仓库是为空的,因此 maven 需要知道一个网络上的仓库,在本地仓库不存在时前往下载网络上的仓库,也就是远程仓库。
(2)中央仓库
当 maven 未配置时,会默认请求 maven 的中央仓库,中央仓库包含了这个世界上绝大多数流行的开源 java 构件,以及源码、作者信息、SCM, 信息、许可证信息等,每个月这里都会接受全世界 java 程序员大概 1 亿次的访问,它对全世界 java 开发者的贡献由此可见一斑。
但是由于最常见的例如网络原因等,国外的中央仓库使用起来并不顺利,因此就有了下载地址为国内的中央仓库,也就是镜像仓库。
(3)镜像仓库
总结来说,镜像仓库就是将国外的中心仓库复制一份到国内,这样一来下载速度以及访问速度都将很快。
(4)私服
一般来说中央仓库或者镜像仓库都能满足我们的需求,但是当我们在公司内合作开发代码时,可能因为系统保密性原因,有一些其他同事开发的外部依赖只希望能够被本公司的人使用,而如果上传到镜像仓库则保密性就不复存在了。因此私服最主要的功能时存储一些公司内部不希望被公开的依赖服务。
1.2 settings 配置详解
settings.xml 配置了本地全局 maven 的相关配置。
以下是一份 seetings.xml 的文件配置顶级元素。
1.2.1 localRepository
用来标识本地仓库的位置
E:\repo\maven
1.2.2 interactiveMode
maven 是否需要和用户交互以获得输入。默认为 true。
true
1.2.3 usePluginRegistry
maven 是否需要使用 plugin-registry.xml 文件来管理插件版本。
false
1.2.4 offline
用来标识是否以离线模式运营 maven。
当系统不能联网时,可以通过次配置来离线运行。
false
1.2.5 servers
当使用 maven 私服时,某些私服需要配置认证信息,需要在此处填写相应的配置。之所以不写在 pom.xml 中是因为一般项目在上传至代码仓库时同样会将 pom.xml 上传,而 setting.xml 一般位于用户本地,因此相对比较安全。其下面可以定义一系列的server子元素,表示当需要连接到一个远程服务器的时候需要使用到的验证方式。这主要有username/password和privateKey/passphrase这两种方式
server001 admin admin123 ${usr.home}/.ssh/id_dsa some_passphrase 664 775 ... deploymentRepo repouser repopwd planetmirror.com PlanetMirror Australia http://downloads.planetmirror.com/pub/maven2 central
其中 id 与 name 用来标识唯一的仓库,url 为镜像仓库地址,mirrorOf 用来匹配当请求什么仓库依赖时使用该镜像。
这里介绍下 配置的各种选项
- *: 匹配所有远程仓库。
- external:*: 匹配所有远程仓库,使用 localhost 的除外,使用 file:// 协议的除外。也就是说,匹配所有不在本机上的远程仓库。
- repo1,repo2: 匹配仓库 repo1h 和 repo2,使用逗号分隔多个远程仓库。
- *,!repo1: 匹配所有远程仓库,repo1 除外,使用感叹号将仓库从匹配中排除。
需要注意的是,由于镜像仓库完全屏蔽了被镜像仓库,当镜像仓库不稳定或者停止服务的时候,Maven 仍将无法访问被镜像仓库,因而将无法下载构件。
此外, maven 读取 mirror 配置是 从上往下读取的,因此谨慎配置 *,因为如果第一个镜像仓库配置了如此标志,那么如果该仓库即使不存在对应依赖也不会向下游查询。
mirrors:用于定义一系列的远程仓库的镜像。我们可以在pom中定义一个下载工件的时候所使用的远程仓库。但是有时候这个远程仓库会比较忙,所以这个时候人们就想着给它创建镜像以缓解远程仓库的压力,也就是说会把对远程仓库的请求转换到对其镜像地址的请求。每个远程仓库都会有一个id,这样我们就可以创建自己的mirror来关联到该仓库,那么以后需要从远程仓库下载工件的时候Maven就可以从我们定义好的mirror站点来下载,这可以很好的缓解我们远程仓库的压力。在我们定义的mirror中每个远程仓库都只能有一个mirror与它关联,也就是说你不能同时配置多个mirror的mirrorOf指向同一个repositoryId。
1》 id:是用来区别mirror的,所有的mirror不能有相同的id
2》 mirrorOf:用来表示该mirror是关联一的哪个仓库,其值为其关联仓库的id。当要同时关联多个仓库时,这多个仓库之间可以用逗号隔开;当要关联所有的仓库时,可以使用“*”表示;当要关联除某一个仓库以外的其他所有仓库时,可以表示为“*,!repositoryId”;当要关联不是localhost或用file请求的仓库时,可以表示为“external:*”。
3》 url:表示该镜像的url。当Maven在建立系统的时候就会使用这个url来连接到我们的远程仓库。
1.2.7 proxies
用来配置代理。proxies:其下面可以定义一系列的proxy子元素,表示Maven在进行联网时需要使用到的代理。当设置了多个代理的时候第一个标记active为true的代理将会被使用。下面是一个使用代理的例子:
... myproxy true http proxy.somewhere.com 8080 proxyuser somepassword *.google.com|ibiblio.org ...
1.2.8 profiles【重要】
根据环境参数来调整构建配置的列表。用于定义一组 profile
seetings 中的 profile 是 pom.xml 中 profile 元素的裁剪版本。
它包含了 id、activation、repositories、pluginRepositories 和 properties 元素。这里的 profile 元素只包含这五个子元素是因为这里只关心构建系统这个整体(这正是 settings.xml 文件的角色定位),而非单独的项目对象模型设置。如果一个 settings.xml 中的 profile 被激活,它的值会覆盖任何其它定义在 pom.xml 中带有相同 id 的 profile。
jdk-1.4 1.8 jdk14 Repository for JDK 1.4 builds http://www.myhost.com/maven/jdk14 default always | | org.myco.myplugins | myplugin | | ${tomcatPath} | | env-dev target-env dev /path/to/tomcat/instance
(1)repositories
定义了一组远程仓库的列表,当该属性对应的 profile 被激活时,会使用该远程仓库。
codehausSnapshots Codehaus Snapshots false always warn http://snapshots.maven.codehaus.org/maven2 default
(2)properties
定义了一组拓展属性,当对应的 profile 被激活时该属性有效。
${user.home}/our-project
(3)id
全局唯一标识,如果一个 settings.xml 中的 profile 被激活,它的值会覆盖任何其它定义在 pom.xml 中带有相同 id 的 profile。
(4)pluginRepositories
同 repositories 差不多,不过该标签定义的是插件的远程仓库。
(5)activation
触发激活该 profile 的条件。
false 1.5 Windows XP Windows x86 5.1.2600 mavenVersion 2.0.3 ${basedir}/file2.properties ${basedir}/file1.properties
1.2.9 ActiveProfiles
在运行时手工激活的 profile。
该元素包含了一组 activeProfile 元素,每个 activeProfile 都含有一个 profile id。任何在 activeProfile 中定义的 profile id,不论环境设置如何,其对应的 profile 都会被激活。如果没有匹配的 profile,则什么都不会发生。
env-test
1.2.10 激活 profile 的三种方式【重要】
上面有提到了两种激活的 profile 的方式,还有一种可以通过命令行激活 profile。
(1)通过 ActiveProfiles 激活
如题 1.2.9 可以同时激活多个 profile。
(2)通过 activation 结果
(3)通过命令行的方式激活
也是我们经常使用的方式,例如:
mvn -P
我们可以通过在 pom.xml 或 setting.xml 中指定不同环境的 profile,在编译构建不同的项目时,通过上述的命令行方式激活对应的 profIle。例如在开发环境下:
mvn package -P dev
可以打包开环环境下的项目。
1.3 Q&A
1.3.1 mirrors 与 repositories 的关系【重要】
从上文可以看到,repository 标签与 mirror 标签都定义了一个远程仓库的位置,那么当一个依赖同时存在于两个仓库时,会先加载那个依赖呢?
这里需要阐述一下 maven 加载真正起作用的 repository 的步骤,
- 首先获取 pom.xml 中 repository 的集合,然后获取 setting.xml 中 mirror 中元素。
- 如果 repository 的 id 和 mirror 的 mirrorOf 的值相同,则该 mirror 替代该 repository。
- 如果该 repository 找不到对应的 mirror,则使用其本身。
- 依此可以得到最终起作用的 repository 集合。可以理解 mirror 是复写了对应 id 的 repository。
mirror 相当于一个拦截器,会拦截被 mirrorOf 匹配到的 repository,匹配原则参照 1.2.6 ,在匹配到后,会用 mirror 里定义的 url 替换到 repository。
没有配置 mirror
配置了 mirror
二、Pom.xml 详解
上章中 setting.xml 定义了某个环境下全局项目的相关依赖配置,而 pom.xml 则具体定义了某一个项目中的依赖配置。
2.1 pom 元素
2.1.1 基本信息
4.0.0 com.companyname.project-group project 1.0 jar itoken dependencies www.funtl.com
基本信息都比较易懂,主要定义了本项目的相关配置说明,例如唯一坐标、版本、项目介绍等。
2.1.2 dependencies【重要】
(1)dependencies
本元素定义了项目中所需要的相关依赖信息,也是最重要的元素之一。
org.apache.maven maven-artifact 3.8.1 spring-core org.springframework true test
(2)如何解决依赖传递问题或 jar 包版本冲突问题
解决上述问题一般有两种方式:
- 当我们作为依赖服务提供者时,可以通过 标签排除掉不想被传递的服务。
... sample.ProjectB Project-B 1.0 true
我们的 A 服务中引用了外部的依赖 B 服务,此时有 A -> B,在别人引用我们时有 C -> A ->B,若此时我们 A 在提供对外服务时不想让别人依赖 B 服务,可以在引用 B 时添加 标签为 true,这样带 C 依赖于 A 时并不会引入 B。如果 C 在此时想要使用 B 的相关服务,需要在 C 的 pom 中显示的调用 B 的相关服务。
- 当我们作为依赖服务使用者时,可以通过 来去除掉我们依赖包中所不想依赖的其他服务。
如果此时我们的 A 服务依赖于 B 服务,B 服务依赖于 C 服务,则有 A ->B ->C,因为某种原因例如 jar 包冲突,我们在 A 中并不想依赖于 C 服务的版本,可以在调用 B 服务时去除掉 C 的相关依赖,然后自己再在 A 中使用 C 的相关版本。
... sample.ProjectB Project-B 1.0 sample.ProjectC Project-C sample.ProjectC Project-C 2.0
2.1.3
当一个服务中存在有多个 moudle 时,每个子 module 都可能引用了相同的 jar 包,但是如果将版本维护到子 module 的 pom 中,当需要升级时需要修改所有的 pom 文件版本。maven 提供了继承的方式来解决此问题。
org.aspectj aspectjweaver 1.0.0
在父 pom 的 中定义子 pom 需要的依赖及版本。
org.springframework.boot spring-boot-starter-parent 1.5.10.RELEASE org.aspectj aspectjweaver
当我们的 pom 中有定义父 pom 的元素后,可以在指定需要的依赖时不指定其版本,maven 会帮我们去父 pom 中查找相关的版本信息。
2.1.4 properties
properties 主要用来定义常量,通过 ${value} 来使用。
1.8 UTF-8 UTF-8 Finchley.RELEASE 2.0.1 2.10.1 org.springframework.cloud spring-cloud-dependencies ${spring-cloud.version} pom import io.zipkin.java zipkin ${zipkin.version}
此外,maven 还通过约定大于配置的方式定义了一些常用的属性。
属性 定义 2.1.5 resources
resources 标签用来标识项目在编译运行时需要额外编译的文件。例如手工引入 jar 包、不同运行环境对应不同的 profile。
src/main/resources true **/*.fxml **/*.yaml src/main/profiles/product true **/*.fxml lib BOOT-INF/lib/ **/*.jar
2.1.6 profile
与 setting.xml 中 profile 所不同的是(参照 1.2.8),pom 中的 profile 有着更多的标签来描述一组环境。从作用上来区分的话,一般 setting.xml 用来标识不同的远程仓库,而 pom 中的 profile 一般用来标识当前项目属于什么环境,如下是一组常见的 pom 中的 profiles。
dev true dev test test src/main/${project.active} true **/*.fxml
此外,一般通过 mvn -P dev/beta/pre/product 命令来激活不同的环境,也可以通过其他的方式来激活 profile,详见 1.2.10。
当然,pom 中的 profile 不止有指定编译环境的功能,同样也可以指定远程仓库等其他功能。
2.1.6 modules
当我们项目中有多个模块时,如果我们要单独打包的话需要在每一个模块都执行对应的 maven 命令,但是通过 标签可以将子服务或平级服务进行聚合,只需要打包该服务,也就会将其对应的子模块同时打包。
springmybatis ../utils
三、null:jrdp-common:null:jar 问题解决
3.1 包找不到问题
我们某次在开发功能的时候,在我们的项目中引用了伏羲另外一个系统的 jar 包,但是预发环境下编译的时候却发现构建失败,原因是
因为当时项目有用到京东自己的仓库,所以我们当时第一反应是去仓库中查找,结果发现仓库中是有这个 jar 包的。
在发现并不是最常见的 jar 包不存在的问题后,我们开始分析报错原因,发现报错的 jrdp-common 这个包并不是我们直接引用的,而是在我们引用的 jar 包中引用的,并且 null:jrdp-common:null:jar 可以推测前面应该是 groupID 与 version。
假设我们的项目是 A 项目,引用的项目是 B 项目,也就是 A->B->jrdp-common
于是我们打开 B 项目,查看 B 的 pom 结构。
发现 B 项目的 pom 中确实引用了 jrdp-common 这个包,但是并没有指定版本,是因为继承了 xx-parent 这个包,我们在这个包中确实也找到了指定的版本号,因此就排除了项目中没有指定 groupid 与版本号的问题。
这个时候好像就问题就陷入了思路,但是我们注意到,我们之前另一个私服上也是有这个包的,那么之前的在另一个私服上引用好像是没有问题的,我们查看了私服了上的 pom 文件,发现也是跟项目一样的。
然后我们就突然想到,会不会是仓库中的包有问题,果不其然
没有指定 parent 也没有指定版本号,于是我们修改了这个版本的 pom,至此问题解决。
总结:因为我们的系统已经被好多个团队中转接手过,因此可能在某些地方踩了不少坑,像这种问题应该就是之前的团队上传了一些有问题的 jar 包,导致依赖不可用,但是因为我们之前一直用的私服是没有什么问题的,只到这次用到了仓库问题才浮现。
另外,此问题并不具有普适性,但是当遇到了 groupid 不能未空的时候可以按照此方法进行排查。
nexus-releases deployment deployment123 nexus-snapshots deployment deployment123 nexus-releases * http://192.168.213.5:8081/content/groups/public/ nexus-snapshots * http://192.168.213.5:8081/content/repositories/snapshots/ nexus nexus-releases http://192.168.213.5:8081/content/groups/public/ true true always nexus-snapshots http://192.168.213.5:8081/content/repositories/snapshots/ true true always nexus-releases http://192.168.213.5:8081/content/groups/public/ true true always nexus-snapshots http://192.168.213.5:8081/content/repositories/snapshots/ true true always nexus
- 当我们作为依赖服务使用者时,可以通过 来去除掉我们依赖包中所不想依赖的其他服务。