麻了! 被 jar 包冲突坑哭了!

麻了! 被 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的)。找出相应的依赖,排掉就好了。我以为快快乐乐的启动就好了,没想到容器启动了,但是没有日志。只是没日志,又不是不能用。

短暂总结下前面的脉络:

alt

Part 2

既然没有日志,咱们就看看jar包列表对比中,差异部分有没有跟log包相关的jar,果不其然,在新的jar包列表中多了 logback-classic 这个jar,见名知意顺手我就把 logback-classic 这个包给排掉了,这下子日志大概好像能显示了吧。

run 启动直接连日志都木得了,不过我丝毫不慌,从启动脚本中copy出启动命令,登录上机器 jar -server 启动一下,我到底看下是什么要么鬼怪。java.lang.ClassNotFoundExceptionch/qos/logback/classic/Level 这不是我刚排查的包里的类吗?我陷入了两难,不排除的话没日志、排除了的话启动不起来。我还在慌乱中,师兄也没解决我的问题,缓缓心情给它过个周末吧,明天再整他,万一他自己好了呢(虽然知道他一定不会好)。

alt

截止到发文前,我终于解决了,本质原因是使用的脚手架封装了logback(基于logback包二次开发),引入logback 两者天然是水火不容,既然原生logback包删除不了,打不过就加入(KD语录),直接把log包直接替换掉,加上logback配置(logback.xml),完美解决。

What?

在实际开发过程中,我们往往会引入一些其他服务的 API 包,调用API 包中的 RPC 服务来达到调用别人服务的目的。在我们的想入引入别人的 jar 包时,总是会遇到基础能力总是会遇到相关基础能力jar包的版本冲突,又或者权限定类名冲突,在对方没有自定义类加载器的时候,我们是需要解决这些冲突问题的,不然再项目运行时就会发生找不到类或者找不到具体的方法。常见的异常有两种:

  1. java.lang.NoSuchMethodError
  2. java.lang.ClassNotFoundException

Why?

冲突的来源源于类加载器没有加载到合适的类,类丢了,或者方法丢了。

举个例子:

alt

比如我们使用的User对象,a、b包的依赖我们都引入了,在默认的类加载器加载的时候,因为我们是按照权限定名来做唯一标识的,如果我们在程序中使用的是a中的User.class ,但是加载的时候加载的是b中的User.class,当我们程序中使用的方法在B中没有时,就会出现java.lang.NoSuchMethodError

还有一种情况:

我们引入jar包的时候一般都是靠groupIdartifactId确定一个依赖包,如下图,a 和 b 的 groupIdartifactId 都一致,只是版本不同,一般情况下Maven打包的时候就会把高版本的给打进去,如果a为高版本,但是程序中引用了 b 中的User.class 运行时就会出现 java.lang.ClassNotFoundException

alt

How?

我们已经知道 jar 包冲突是什么?并且知道是什么引起 jar 包冲突了,接下来我们看一下如何去解决 jar 包冲突。

确定冲突依赖包

启动时我们已经看到,java.lang.ClassNotFoundExceptionch/qos/logback/classic/Level 这样的异常信息,在IDEA 中搜索到这个类,并且定位到依赖包。

首先确定依赖包,然后确定pom信息,pom中包含 groupId 和 artifactId 和version信息。有了这些信息,我们就可以找到具体都那些pom依赖,把那些新进并且没有用的依赖排除掉。

找到有问题的依赖

如果依赖较少,直接把依赖树打出来,然后根据树形关系,排掉冲突的依赖即可。

  1. 分析当前pom依赖树 mvn dependency:tree
  2. 人肉搜索,确定冲突

当然依赖树也是有筛选功能的,并且可以直接展示冲突信息。

mvn dependency:tree > dependency_tree.txt -Dverbose -Dincludes={groupId}:{artifactId }

这个就比较厉害了,直接根据 groupIdartifactId 筛选出相关的 dependency 并在下方直接 exclusion 即可,排除示例:

        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>${hutool.version}</version>
            <exclusions>
                <exclusion>
                    <groupId>org.projectlombok</groupId>
                    <artifactId>lombok</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

格式类似于这样的。

总结

jar 包冲突在开发过程中是不可避免的,为了不挨骂(给小伙伴埋坑),我这里有几个建议。

  1. api 包定义尽量简单纯粹。(尽量不要包含一些跟api定义本身无关的包,例如: spring全家桶、 log全家桶等)。
  2. 经常看下依赖树是否存在大量的依赖冲突,并修正。(可以安装 meven helper插件便于查看依赖冲突)
  3. 日常开发使用快照包版本,上线发布前必须改为 release版本(避免快照版本变更造成线上启动问题、线上事故等)。

忙时做业绩,闲时修内功。我是janker。咱们下期见。

本文由 mdnice 多平台发布

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值