版本不兼容Jar包冲突该如何是好?
一、引言
“老婆”和“妈妈”同时掉进水里,先救谁?
常言道:编码五分钟,解冲突两小时。作为Java开发来说,第一眼见到ClassNotFoundException、NoSuchMethodException这些异常来说,第一反应就是排包。经过一通常规和非常规操作以后,往往会找到同一个Jar包引入了多个不同的版本,这时候一般排除掉低版本、保留高版本就可以了,这是因为一般Jar包都是向下兼容的。但是,如果出现版本不兼容的情况的时候,就会陷入“老婆和妈同时掉进水里,先救谁”的两难境地,如果恰恰这种不兼容发生在中间件依赖和业务自身依赖之间,那就更难了。
如下图所示,Project表示我们的项目,Dependency A表示我们的业务依赖,Dependency B表示中间件依赖,如果业务依赖和中间件依赖都依赖同一个Jar包C,但是版本却不一样,分别为0.1版本和0.2版本,而且最不巧的是这两个版本还存在冲突,有些老的功能只在0.1低版本中存在,有些新功能只在0.2高版本中存在,真是“老婆和妈同时掉进水里,先救谁都不行”。
(图片摘自:SOFAArk官网)
俗话说:没有遇到过Jar包冲突的开发,一定是个假Java开发;没有解决过Jar包冲突的开发,不是一个合格的Java开发。在最近的项目里,我们需要使用Guava的高版本Jar包,但是发现中间件依赖的是低版本且与高版本不兼容的Jar包,面对这种两难,我们肯定是“老婆”和“妈妈”都要救,于是我们开始寻求解决方案。
二、不兼容依赖冲突解决方案
“老婆”和“妈妈”都要救,怎么救?
首先,我们想到的是,能不能把需要用到的Guava高版本的代码拷出来直接放到我们的工程中去,但是这样做会带来几个问题:
-
Guava作为一个功能丰富的基础库,某一部分的代码往往与其他很多代码都存在依赖关系,这会造成牵一发而动全身,工作量会比预想的要大很多;
-
拷贝出来的代码只能自己手动维护,如果官方修复了问题或者重构了代码或者增加了功能,我们想要升级的话,那么只能重头再来一遍。于是,我们只能另外想其他的方案,这个只能作为最后的兜底方案。
然后,我们在想,一个Java类被加载到JVM虚拟机里区别于另一个Class,其一是它们俩全路径不一样,是风马牛不相及的两个不同的类,但却是被不同的类加载器加载的,在JVM虚拟机里它们仍然被认为是两个不同的Class。所以,我们就在想从类加载器上来寻求解决方案。在阿里巴巴内部,有一个Pandora的组件,正如其名就像一个魔盒,它会把中间件的依赖都装到Pandora里(内部叫做Sar包),这样的话,就能避免在中间件和业务代码直接出现“老婆和妈同时掉进水里,先救谁”的两难境地。
同样,在类似的场景比如应用合并部署也能发挥威力。但是Pandora只在阿里内部使用并未开源。在蚂蚁金服,也有一个这样的组件,并且开源了,叫做SOFAArk(官方网址,感兴趣的可以去官网了解SOFAArk的原理和使用),我们感觉已经找到了那个Mr.Right,于是我们开始研究SOFAArk如何使用。和Pandora一样,SOFAArk也是通过使用不同的 ClassLoader 加载不同版本的三方依赖,进而隔离类,彻底解决包冲突的问题,这就要求我们需要将相关的依赖打包成Ark Plugin(参见SOFAArk官方文档)。
对于公司来说,这样的方案收益是比较大的,打包成Ark Plugin后整个公司都能够共享,业务方都能受益,但是对于我们一个项目来说,采用这样的方案无疑过重了。于是,我们与中间件同学联系,询问是否有计划引入类似的隔离组件解决中间件和业务代码之间的依赖冲突问题,得到的答复是公司目前包冲突并不是一个强烈的痛点,暂时没有计划引入。于是,我们只能暂且搁置SOFAArk,继续寻找新的解决方案。
接着,我们在想既然Pandora/SOFAArk采用类加载隔离了同一路径的类,那么如果我们把冲突的两个版本库的groupId变得不一样,那么即使同名的类全路径也是不一样的,这样在JVM里面必然是不同的Class。如果把Pandora/SOFAArk的隔离方式称之为逻辑隔离的话,这种就相当于物理隔离了。要实现这一点,借助IDE的重构功能或者全局替换的功能就能比较容易的实现这一点。
正在我们准备撸起袖子动手干的时候,我们不禁在想,这样的痛点应该早就有人遇到,尤其像Guava、Commons这类的基础类库,冲突在所难免,前人应该已经找到了优雅的挠痒姿势。于是,我们就去搜索相关的文章,果不其然,maven-shade-plugin正是那优雅的挠痒姿势,这个Maven插件的原理正是将类的包路径进行重新映射,达到隔离不兼容Jar包的目的。
三、maven-shade-plugin解决依赖冲突
最后如何来配置和使用maven-shade-plugin将Guava映射成我们自己定制的Jar包,实现与中间件Guava的隔离。整个的过程还是比较清晰明了的,主要是创建一个Maven工程,引入依赖,配置我们要发布的仓库地址,引入编译打包插件和maven-shade-plugin插件,配置映射规则(标签之间部分),然后编译打包发布到Maven仓库。pom.xml的配置如下:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.shaded.example</groupId> <artifactId>guava-wrapper</artifactId> <version>${guava.wrapper.version}</version> <name>guava-wrapper</name> <url>https://example.com/guava-wrapper</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <!- 版本与 guava 版本基本保持一致 -> <guava.wrapper.version>27.1-jre</guava.wrapper.version> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>27.1-jre</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.3</version> <configuration> <source>${maven.compiler.source}</source> <target>${maven.compiler.target}</target> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>2.3.2</version> <executions> <execution> <id>default-jar</id> <goals> <goal>jar</goal> </goals> <phase>package</phase> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-source-plugin</artifactId> <version>2.4</version> <executions> <execution> <id>default-sources</id> <goals> <goal>jar-no-fork</goal> </goals> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>2.4.1</version> <configuration> <createDependencyReducedPom>false</createDependencyReducedPom> </configuration> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <!-- 重命名规则配置 --> <relocations> <relocation> <!-- 源包路径 --> <pattern>com.google.guava</pattern> <!-- 目标包路径 --> <shadedPattern>com.google.guava.wrapper</shadedPattern> </relocation> <relocation> <pattern>com.google.common</pattern> <shadedPattern>com.google.common.wrapper</shadedPattern> </relocation> <relocation> <pattern>com.google.thirdparty</pattern> <shadedPattern>com.google.wrapper.thirdparty</shadedPattern> </relocation> </relocations> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"/> </transformers> </configuration> </execution> </executions> </plugin> </plugins> </build> <distributionManagement> <!- Maven仓库配置,略 -> </distributionManagement> </project>
项目引入这个新打包的guava-wrapper后,import选择从这个包导入我们需要的相关类即可。如下:
<dependency> <groupId>com.vivo.internet</groupId> <artifactId>guava-wrapper</artifactId> <version>27.1-jre</version> </dependency>
四、结语
为了在同一个项目中使用多个版本不兼容的Jar包,我们首先想到手动自行维护代码,但是工作量和维护成本很高,接着我们想到通过类加载器隔离(开源方案SOFAArk),但是需要将相关依赖都打包成Ark Plugin,解决方案无疑有点过重了,最后通过maven-shade-plugin插件重命名并打包,优雅地解决了项目中不兼容多个版本Jar包的冲突问题。从问题出来,我们一步一步探寻问题的解决方案,最终的maven-shade-plugin插件方案虽然看似与手动自行维护代码本质一致,看似回到了原点,但其实最终的方案优雅性远比最开始高得多,正如人生的道路那样,螺旋式上升,曲线式前进。
如果遇到类似需要支持版本不兼容Jar包共存的场景,可以考虑使用maven-shade-plugin插件,这种方法比较轻量级,可用于项目中存在个别不兼容Jar包冲突的场景,简单有效,成本也很低。但是,如果Jar包冲突现象比较普遍,已成为明显或者普遍的痛点,还是建议考虑文中提到的类似Pandora、SOFAArk等类加载器隔离的方案。
作者:vivo互联网服务器团队-Zhang Wei

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
物流巨头DW Morgan暴露了100 GB 客户数据
Hack Read网站披露,Website Planet安全团队发现了一个配置错误的亚马逊S3“存储池”,池中包含约250万个文件,大小超过100GB。研究表明,该数据池属于应链管理和物流巨头D.W.Morgan公司,该公司总部位于加利福尼亚州,业务遍及全球。 安全研究人员称,数据库中详细记录了D.W. Morgan企业员工和其全球客户的财务、运输、个人和敏感数据信息,其中更是有500强爱立信和财富500强思科。 值得一提的是,该数据库最早于2021年11月12日已经被发现,但细节表明,上周才被Website Planet 披露。 暴露的数据详情 糟糕的是,该存储池没有任何安全身份验证或密码的情况下仍然向公众公开,意味着任何了解 AWS 存储桶功能的人都可以轻松访问数据。 暴露的数据类型如下所示: 签名; 全名 ; 附件; 电话号码; 订购的货物 ; 货物损坏; 流程照片; 流程细节; 账单地址; 发票的日期; 船运条码; 未知文件; 交付地址; 设施位置; 货物的照片; 为货物支付的价格; 包装标签的照片; 现场文件的图片; 运输计划和协议。 暴露数据示例截图 下面屏幕截图是公开的数...
- 下一篇
一行降低 100000kg 碳排放量的代码!
文|张稀虹(花名:止语 ) 蚂蚁集团技术专家 负责蚂蚁集团云原生架构下的高可用能力的建设 主要技术领域包括 ServiceMesh、Serverless 等 本文 3631 字 阅读 8 分钟 PART. 1 故事背景 今年双十一大促后,按照惯例我们对大促期间的系统运行数据进行了详细的分析,对比去年同期的性能数据发现,MOSN 的 CPU 使用率有大约 1% 的上涨。 为什么增加了? 是合理的吗? 可以优化吗? 是不可避免的熵增,还是人为的浪费? 带着这一些列灵魂拷问我们对系统进行了分析 PART. 2 问题定位 我们从监控上发现,这部分额外的开销是在系统空闲时已有,并且不会随着压测流量增加而降低,CPU 总消耗增加 1.2%,其中 0.8% 是由 cpu_sys 带来。 通过 perf 分析发现新版本的 MOSN 相较于老版本, syscall 有明显的增加。 经过层层分析,发现其中一部分原因是 MOSN 依赖的 sentinel-golang 中的一个StartTimeTicker 的 func 中的 Sleep 产生了大量的系统调用,这是个什么逻辑? PART. 3 理论分析 查...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- 设置Eclipse缩进为4个空格,增强代码规范
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- SpringBoot2全家桶,快速入门学习开发网站教程
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- Eclipse初始化配置,告别卡顿、闪退、编译时间过长
- CentOS8安装Docker,最新的服务器搭配容器使用
- Docker安装Oracle12C,快速搭建Oracle学习环境
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7