麻了! 被 jar 包冲突坑哭了!
一杯茶一包烟,一行代码写一天
听说这个是编程大佬的行为准则。而我没有茶没有烟,但是一行代码写了一天,(算起来可能不止一天)。
大家好,我是janker。今天来聊下关于jar包冲突的事儿。
Part 1
最近接了一个产品需求:
永哥,最近业务方提出了一个诉求,就是让我们把某某功能开放给他们,入口参数就只需要添加一个字段,我都调研过了,我们系统都是支持的。
一看到这样的话术,作为接过很多一句话产品需求的爬坑程序员,我建议你们不要乱答应。因为很有可能加一个字段,你加了一天,并且看不到产出。但是耳根子很软的永哥忍不住接了。
一般这种需求,都是要升级jar包的,加字段三板斧,改依赖版本、加字段、发布。带上测试环境部署的时间,目测15分钟就差不多了,自测一下,20分钟怎么也够了。自信如我,如风随性。
`but` 奈何部署了`20`分钟,容器重启了好几次一直不成功,熟悉的味道,记得上次出现这个情况还是在上次,问题不大,看下错误日志,没有日志、只有一个简单的`Main`函数日志,然后就没然后了。
一般出现这种情况的,多半是lib包依赖的时候,有冲突。为了验证我的猜想,我部署了一下mastar
分支,竟然也启动不起来,what
?是谁动了我的 jar
包,一边逼逼赖赖,一边继续修bug。思来想去,只有一种可能,会影响到master
分支的部署,那就是我们引用了快照包,因为快照包在我们上线 master
的时候可能是好的,但是我们在发布到测试环境的时候快照包做了更改,导致启动不了。到了这里只有一招可以用了,就是对比打包lib
文件夹下的jar包,多出来的就是罪魁祸首。最终,搞了个jar
包对比脚本,发现新的包里多出来了 xxx.jar
,我们只知道多了 xxx.jar
怎么知道是哪个依赖引起的,办法有两种:1. 反解jar
包,找到MANIFEST.MF
里面有相应的包信息,然后结合 class
很容易就知道哪个依赖包影响的 2. 根据jar
包名,见名知意猜测一下(针对常见的jar包完全是ok的)。找出相应的依赖,排掉就好了。我以为快快乐乐的启动就好了,没想到容器启动了,但是没有日志。只是没日志,又不是不能用。
短暂总结下前面的脉络:
Part 2
既然没有日志,咱们就看看jar包列表对比中,差异部分有没有跟log包相关的jar
,果不其然,在新的jar包列表中多了 logback-classic
这个jar
,见名知意顺手我就把 logback-classic
这个包给排掉了,这下子日志大概好像能显示了吧。
run 启动直接连日志都木得了,不过我丝毫不慌,从启动脚本中copy出启动命令,登录上机器 jar -server
启动一下,我到底看下是什么要么鬼怪。java.lang.ClassNotFoundException
,ch/qos/logback/classic/Level
这不是我刚排查的包里的类吗?我陷入了两难,不排除的话没日志、排除了的话启动不起来。我还在慌乱中,师兄也没解决我的问题,缓缓心情给它过个周末吧,明天再整他,万一他自己好了呢(虽然知道他一定不会好)。
截止到发文前,我终于解决了,本质原因是使用的脚手架封装了logback
(基于logback
包二次开发),引入logback
两者天然是水火不容,既然原生logback
包删除不了,打不过就加入(KD语录)
,直接把log
包直接替换掉,加上logback
配置(logback.xml
),完美解决。
What?
在实际开发过程中,我们往往会引入一些其他服务的 API
包,调用API
包中的 RPC
服务来达到调用别人服务的目的。在我们的想入引入别人的 jar
包时,总是会遇到基础能力总是会遇到相关基础能力jar
包的版本冲突,又或者权限定类名冲突,在对方没有自定义类加载器的时候,我们是需要解决这些冲突问题的,不然再项目运行时就会发生找不到类或者找不到具体的方法。常见的异常有两种:
-
java.lang.NoSuchMethodError
; -
java.lang.ClassNotFoundException
;
Why?
冲突的来源源于类加载器没有加载到合适的类,类丢了,或者方法丢了。
举个例子:
比如我们使用的User
对象,a、b
包的依赖我们都引入了,在默认的类加载器加载的时候,因为我们是按照权限定名来做唯一标识的,如果我们在程序中使用的是a
中的User.class
,但是加载的时候加载的是b
中的User.class
,当我们程序中使用的方法在B中没有时,就会出现java.lang.NoSuchMethodError
。
还有一种情况:
我们引入jar包的时候一般都是靠groupId
和artifactId
确定一个依赖包,如下图,a 和 b 的 groupId
和 artifactId
都一致,只是版本不同,一般情况下Maven
打包的时候就会把高版本的给打进去,如果a为高版本,但是程序中引用了 b 中的User.class
运行时就会出现 java.lang.ClassNotFoundException
。
How?
我们已经知道 jar 包冲突是什么?并且知道是什么引起 jar 包冲突了,接下来我们看一下如何去解决 jar 包冲突。
确定冲突依赖包
启动时我们已经看到,java.lang.ClassNotFoundException
,ch/qos/logback/classic/Level
这样的异常信息,在IDEA 中搜索到这个类,并且定位到依赖包。
首先确定依赖包,然后确定pom信息,pom中包含 groupId 和 artifactId 和version信息。有了这些信息,我们就可以找到具体都那些pom依赖,把那些新进并且没有用的依赖排除掉。
找到有问题的依赖
如果依赖较少,直接把依赖树打出来,然后根据树形关系,排掉冲突的依赖即可。
- 分析当前pom依赖树
mvn dependency:tree
- 人肉搜索,确定冲突
当然依赖树也是有筛选功能的,并且可以直接展示冲突信息。
mvn dependency:tree > dependency_tree.txt -Dverbose -Dincludes={groupId}:{artifactId }
这个就比较厉害了,直接根据 groupId
和 artifactId
筛选出相关的 dependency
并在下方直接 exclusion
即可,排除示例:
cn.hutool
hutool-all
${hutool.version}
org.projectlombok
lombok
格式类似于这样的。
总结
jar 包冲突在开发过程中是不可避免的,为了不挨骂(给小伙伴埋坑),我这里有几个建议。
- api 包定义尽量简单纯粹。(尽量不要包含一些跟api定义本身无关的包,例如:
spring
全家桶、log
全家桶等)。 - 经常看下依赖树是否存在大量的依赖冲突,并修正。(可以安装
meven helper
插件便于查看依赖冲突) - 日常开发使用快照包版本,上线发布前必须改为
release
版本(避免快照版本变更造成线上启动问题、线上事故等)。
忙时做业绩,闲时修内功。我是janker。咱们下期见。
本文由mdnice多平台发布