Maven 可能会出现的一些问题及排查方法。
现在的大部分 Java 应用基本都是通过 Maven 进行组织的,不论是分布式应用还是单体集群应用往往都会通过一个父 POM 加若干子 POM 完成项目的组织。然而这种多应用多模块的拆分就带来了一个巨大的体力成本——发包
举个例子,说明下为什么会出现这种情况:
上面这个图中有两个应用 portal 和 dump,其中 portal 的四个包是需要对外引用的也就是说 client 、domain、common、log 这几个包是两个应用共享的二方包。而共享不可避免的会带来竞争!
简单分析会有如下的问题:
为了解决上面遇到的种种问题,怎么做才能让这种频繁的 发包,替换版本,解决冲突 的流程更加简便自动化呢?简单来讲我的思路是 集中式版本控制!是不是听着很耳熟,和大名鼎鼎的 git 的思路刚好相反,接下来就一起来看如何让流程优雅起来以及踩到 Maven 的一些大坑后又是如何一步步爬起来的。
在此之前我们先看看 Maven 项目到底是如何对模块和包进行组织的。
首先创建一个 Maven 项目,然后在通过上图的三步你就能完成一个新模块的创建。
结果你会得到如上图所示的一个父 POM 和两个子 POM。
父 POM 核心内容如下:
分为两个部分,一个部分是父 POM 的声明,包含 GAV 坐标,打包方式必须为 POM,因为需要使用聚合模型,另外一部分就是父工程管理的子模块 modules 标签。
子 POM 相对要更简单:
声明自己的父模块是谁,以及自己的 GAV 坐标,可能细心的你发现了这里他并没有写 GroupId 和 Version 这是因为父工程已经声明了,如果没有特别的版本号和 groupId 的要求直接继承父工程的内容。
Maven 支持通过父 POM 中的依赖继承的方式避免开我们手动指定依赖库的版本。但是传递依赖会导致依赖图迅速增长的特别大,所以 Maven 对于传递依赖有一定的限制:
依赖项的范围决定了什么时候这些依赖会被加载进去,在 Jar 瘦身等操作的时候特别有用,同时解决依赖冲突也是一把好手
当前项目为 A,A依赖于B,B依赖于C。知道B在A项目中的scope,那么怎么知道C在A中的scope呢?这个就需要根据 nexus 的一张表来确定:
比如 A 依赖 B 的范围为 provided ,B 依赖 C 的范围为 runtime 的 最终 A 依赖 C 的范围为 provided
在回到我们一开始提出的问题,如果团队里三个人开发同一个应用,大家都需要修改二方包的版本号,分支合并一定会冲突。同时引用这个二方包的应用也一定会冲突,因为大家使用的版本号一般都不同,那么以谁的为准?谁来解决这个冲突?往往因为版本号的问题导致冲突合并半小时应用都不一定可以构建的起来。
同时在发布上线的时候要改包为正式包,需要替换很多个地方,大家的版本还需要一致,往往需要解决多个地方的版本冲突。
为了解决这个问题,我采用了如下的方案:
改造前后,主 POM样子如下:
子 POM 中就不在单独声明版本号了 而是直接继承父 POM 中定义的版本号:
这样确实很好的解决了上面的两个问题,但是在某次部署过程中遇到了一个非常诡异的问题。
我们项目结构如下:
ProjA
| -- Apache Commons 3.0
|________
| Proj B's Client
| | -- mq-client
| | -- redis-client
| | -- etc.
|
|________
Server
| -- Server Libraries
| -- etc.
A 工程引用了 B 工程的 client 包,而其 client 包中引入了 mq 和 redis 的客户端,因此 A 工程在不用引入这两个包的情况下可以直接使用这两个包中的类。但是在某次部署的过程中,A 工程怎么都找不到 mq 和 redis 的类文件,这就让人摸不着头脑了,线上都是可以的,为何预发就有这个问题了???
又到了紧张而又刺激的问题排查阶段了。从 mvn 仓库上下载了最新的编译后的包放到 jad 中发现代码都是和我的分支保持一致的,没有啥问题,而且看到 snapshot 包后面的时间戳也是我发布包的时间戳。
那也就是发包的过程和结果都没啥问题,肯定是拉包的时候出问题了呗,看看拉包的过程是否有异常。
mvn clean && mvn install -fn
一套命令跑下来,好像也没有 error,但是包就是拉不下来。看看日志里面有什么猫腻吧!一顿日志的搜查发现了一行 waring 日志:应用引入的依赖包无效,依赖包中传递依赖项不可用,可以通过开启debug获取更多信息。
[WARNING] the POM for A is invalid, transitive dependencies (if any) will not be available, enable debug logging for more details...
开启maven debug功能后,警告后紧跟了一条错误信息,如下。
[WARNING] The POM forxx:jar:1.0-SNAPSHOT is invalid, transitive dependencies (if any) will not be available: 2 problems were encountered while building the effective model for xx:1.0-SNAPSHOT
[ERROR] 'dependencies.dependency.version' for xx:jar is missing.
[ERROR] 'dependencies.dependency.version' for xx:jar is missing.
transitive dependencies 这玩意不就是依赖传递么,我已开始还不知道遇到的这个问题如何用文字向搜索引擎描述,现在显然就是传递依赖的一些包没有被引入啊,这不就找到问题所在了, 因为下面有两个包没有声明 jar 的包版本。
但是为何会出现这个问题呢?根据上述报错的关键字我在 stackoverflow 中找到了答案:
One reason for this is when you rely on a project for which the parent pom is outdated. This often happens if you are updating the parent pom without installing/deploying it.
To see if this is the case, just run with mvn dependency:tree -X
and search for the exact error. It will mention it misses things you know are in the parent pom, not in the artifact you depend on (e.g. a jar version).
The fix is pretty simple: install the parent pom using mvn install -N
and re-try
上面短短几句话即说明了原因也给出了解决方案,美利坚的程序员果然牛皮!描述的大致意思就是因为这个二方包的父 POM 用的是老版本里面没有包含一些传递依赖的 jar 包的版本导致很多包拉不下来。解决方案也很简单直接把父 POM 中的依赖版本号加上并重新打包发布下就好了。
回顾上面说的组件的传递依赖,这里的二方包中依赖的 redis 和 mq 的 client 包没有拉下来是因为二方包 POM 中的某个 jar 的版本号即没有在父 POM 中定义也没有在二方 POM 中定义。二方包在找组件的依赖的时候首先会在本 POM 找,如果没有找到就会根据
<parent>
<artifactId>module-test</artifactId>
<groupId>org.example</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
声明的父 POM 的版本号去父 POM 中找,因为父 POM 用的老版本里面根本没有那个包的版本号所以就报了刚才那个错误。
所以如果要发布新的二方包而且想要使用传递依赖的特性的话一定要重新发布父 POM !!!
Copyright© 2013-2020
All Rights Reserved 京ICP备2023019179号-8