• jar包冲突与运行期报错


    目录

    前言

    现象

    分析

    原因分析

    Jar包冲突的原理

    Maven依赖传递原则

    Maven依赖实例分析

    编译期不报错而运行期报错的原因


    前言

    相信jar包冲突问题是Java工程师经常遇到的问题之一。

    说来惭愧,作为一名多年coding经验的老工程师,之前一直得过且过,没有仔细分析这个问题。

    现象

    经过一轮新的迭代,各功能在开发和测试环境验证均没问题,但上线后突然报错,关键日志摘录如下:

    1. Caused by: java.lang.NoSuchMethodError: reactor.core.publisher.Mono.contextWrite(Lreactor/util/context/ContextView;)Lreactor/core/publisher/Mono;
    2. at com.shaded.azure.core.http.rest.RestProxy.handleRestReturnType(RestProxy.java:548)
    3. at com.shaded.azure.core.http.rest.RestProxy.invoke(RestProxy.java:148)
    4. at com.sun.proxy.$Proxy182.upload(Unknown Source)
    5. at com.shaded.azure.storage.blob.implementation.BlockBlobsImpl.uploadWithResponseAsync(BlockBlobsImpl.java:395)
    6. ... 77 more

    排查发现是该轮迭代新增的存储功能抛错,项目依赖了某个SDK,该SDK对用户屏蔽了底层存储(Ceph/Azure Blob/AWS S3等)的差异,但是测试环境和生产环境的底层存储介质不同,测试阶段没有覆盖到这些不同的介质。

    这就是jar包冲突的经典在线场景:1、Java项目编译不报错;2、在A环境下运行正常;3、切换到B环境后运行报错(NoSuchMethodError、ClassNotFoundException等最常见)。

    分析

    首先梳理了下我司现有Java项目构建发布的流程(我司依赖某款自研工具进行自动进行),该流程分位两个阶段,构建和部署,截取构建阶段关键信息如下:

    1. ------------------------- [ Check Out Code ] -------------------------
    2. check out code 1660554686391
    3. /xxxxxx/281534
    4. Cloning into 'xxxxxxx'...
    5. Note: checking out 'f649b4102ff5266f5d213c9e6562831fb237adcd'.
    6. You are in 'detached HEAD' state. You can look around, make experimental
    7. changes and commit them, and you can discard any commits you make in this
    8. state without impacting any branches by performing another checkout.
    9. If you want to create a new branch to retain commits you create, you may
    10. do so (now or later) by using -b with the checkout command again. Example:
    11. git checkout -b
    12. HEAD is now at f649b410 refine es code
    13. ------------------------- [ Finish Check Out Code ] -------------------------
    14. ------------------------- [ Build Package ] -------------------------
    15. build package 1660554688525
    16. ------------------------- [ Excute mvn clean package ..... ] -------------------------
    17. mvn clean package -U -B -DskipTests
    18. [INFO] Scanning for projects...
    19. [INFO] Downloading from nexus: http://nexus.xxx.com/content/groups/public/com/envisioniot/parent-pom/1.0.0-SNAPSHOT/maven-metadata.xml
    20. [INFO] Downloaded from nexus: http://nexus.xxx.com/content/groups/public/com/envisioniot/parent-pom/1.0.0-SNAPSHOT/maven-metadata.xml (604 B at 3.2 kB/s)
    21. .......
    22. ------------------------- [ Finish Build Package ] -------------------------
    23. finish build package 1660554791437
    24. ------------------------- [ Build Docker Image ] -------------------------
    25. build docker image 1660554791441
    26. WARNING! Using --password via the CLI is insecure. Use --password-stdin.
    27. WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
    28. Configure a credential helper to remove this warning. See
    29. https://docs.docker.com/engine/reference/commandline/login/#credentials-store
    30. Login Succeeded
    31. ------------------------- [ Building Docker Image harbor-alpha1.xxx.io... ] -------------------------
    32. docker build -f Dockerfile -t harbor-alpha1.xxx.io/xxx/xxx:feature_branch_2208_20220815091119 . --pull
    33. Sending build context to Docker daemon 435.9MB
    34. ------------------------- [ Finish Build Docker Image ] -------------------------
    35. finish build docker image 1660554831194
    36. ------------------------- [ Push Docker Image ] -------------------------
    37. push docker image 1660554831200
    38. The push refers to repository [harbor-alpha1.xxx.io/xxx/xxx]
    39. 74c17a129617: Preparing
    40. ........
    41. 258ddc74925c: Pushed
    42. feature_branch_2208_20220815091119: digest: sha256:68ecae221c0881ca47180ce73c25671c501fd8f4fa1c32dc9843de875939ca58 size: 2422
    43. ------------------------- [ Finish push Docker Image ] -------------------------

    简单分析可见构建阶段大致进行了如下操作:

    1. 拉取代码(git checkout)
    2. Maven构建(mvn clean package -U -B -DskipTests)
    3. 打包docker镜像
    4. 上传镜像到harbor仓库

    部署阶段则是将harbor仓库的镜像部署到K8s的过程,这里就不做分析了。

    下面开始思考这个问题:

    为什么jar包冲突在编译期发现不了,到了运行期才能被被发现呢?

    原因分析

    Jar包冲突的原理

    假设我们项目中依赖了A和B两个Jar包。而A和B各自又有以下传递依赖

    A -> X -> Z(2.0)

    B -> X -> Y -> Z(2.5)

    那最终系统中Z包就产生了冲突,2.0和2.5两个版本冲突。但是classpath中只会依赖一个版本的Z包。根据传递依赖的最短路径优先原则,最终依赖的应该是2.0版本。

    Maven依赖传递原则

    先从Maven工具开始,公开资料显示,几乎所有的Jar包冲突都和依赖传递原则有关:

    最短路径优先原则

    假如引入了2个Jar包A和B,都传递依赖了Z这个Jar包:

    A -> X -> Y -> Z(2.5)

    B -> X -> Z(2.0)

    那其实最终生效的是Z(2.0)这个版本。因为他的路径更加短。如果我本地引用了Z(3.0)的包,那生效的就是3.0的版本。一样的道理。

    最先声明优先原则

    如果路径长短一样,优先选最先声明的那个。

    A -> Z(3.0)

    B -> Z(2.5)

    这里A最先声明,所以传递过来的Z选择用3.0版本的。

    Maven依赖实例分析

    1. 先借助mavn命令对某个工程进行构建,运行命令mvn clean package -Dmaven.test.skip=true
    2. 找到其构建好的工程jar包,并进行解压,解压后的项目如图所示
    3. 借助maven helper插件对某个maven工程进行分析
    4. 上图显示HdrHistogram这个包存在jar包冲突的现象,分别有4个不同的jar包带入了2.1.9和2.1.12的2种不同的jar包版本,而根据最短路径优先原则,Maven最终选取了第4个包中的依赖,也就是2.1.9这个版本
    5. 继续分析第二个发生了冲突的包
    6. 上图显示cat-core这个包存在jar包冲突的现象,分别有2个不同的jar包带入了1.3.9和1.3.10的2种不同的jar包版本,而他们的依赖深度一致,此时最短路径优先原则失效,而根据最先声明原则,上面的一个jar包早于下面的jar包声明,所以Maven最终选取了上面jar包中的依赖,也就是1.3.9这个版本

    编译期不报错而运行期报错的原因

    最后回到最初的疑问,为什么jar包冲突的问题不会在编译期报错呢,我的判断是:发生冲突的jar包都是些已经编译好的.class压缩包,java工程的编译是个.java -> .class的过程,它在编译期只检查.java文件,假如.java引用了jar包里的A Class的a方法,如果a方法存在,编译就能过。但如果a方法又引用了jar包里其他的b方法,而这个b方法实际存不存在,这就不在编译期的检查范围了。

    总结

    最后总结一下可能导致NoClassDefFoundError产生的几种原因:

    1. Maven中对依赖包的声明为provided,但运行期容器并未提供对应的jar
    2. Maven中嵌套依赖了同一个包的不同版本,但在Maven编译打包的时候排掉了某个版本
    3. 不同jar包中提供了class名称相同的类

    参考资料:

    用好这几个技巧,解决Maven Jar包冲突易如反掌 - SegmentFault 思否

  • 相关阅读:
    跨境电商热潮:宣传与真实情况有何不同?
    【优化调度】基于NSGAII算法的车辆充电调度策略研究含Matlab代码
    四、【React-Router5】样式丢失问题
    CLIP 论文逐段精读【论文精读】
    【python】内置函数汇总
    Filter快速入门、Filter执行流程、Filter使用细节、Listener概念、分类、ServletContextListener使用
    汽车售后接待vr虚拟仿真实操演练作为岗位培训的重要工具和手段
    LeetCode1 两数之和
    这种动态规划你见过吗——状态机动态规划之股票问题(中)
    2022-10-27 工作记录--Swiper/React-解决swiper处理动态数据出现样式紊乱、抖动问题
  • 原文地址:https://blog.csdn.net/Lakers_KobeBryant/article/details/126352839