用rust 写一个jar包 class冲突检测工具
Rust很适合写命令行工具,特别是使用clap crate 更加方便,这篇文章介绍使用rust写一个jar包class冲突检测的工具。项目地址: https://github.com/Aitozi/jar_conflict_detector
首先jar包class冲突的现象是多个jar包中有同名的class,并且class的md5还不一样,那么就意味着该class存在多个版本,那么就存在冲突的可能。
思路比较简单,就是遍历每个jar包,记录ClassName 和 对应 CRC 校验码 及 jar 包的对应关系。
通过clap的derive api就可以快速定义个命令行的参数解析器。
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)]
struct Args {
#[arg(
short,
long = "jars",
required = true,
help = "The jar list joined by semicolon"
)]
jar_list: String,
#[arg(long, help = "Disable the crc check", action = clap::ArgAction::SetTrue)]
#[arg(default_value_t = false)]
disable_crc: bool,
#[arg(short, long, action = clap::ArgAction::Append, help = "The exclude package prefix")]
exclude: Vec<String>,
}
通过zip读取jar包中的entry, 过滤只处理.class文件,并从zip_file中读取crc32的元数据,这样可以避免读取原始数据生成md5,可以大大加快处理速度。
中间编写的时候遇到了一个常见的rust borrow checker的问题。
以下代码为例
fn main() {
let path = "/tmp/a.jar";
let jar = File::open(path).unwrap();
let mut zip = ZipArchive::new(jar).unwrap();
for name in zip.file_names() {
let entry = zip.by_name(name);
println!("name: {}, size: {}", name, entry.unwrap().size());
}
}
我是想通过遍历ZipArchive#file_names然后根据文件名获取ZipFile但是会有如下编译错误

pub fn file_names(&self) -> impl Iterator<Item = &str> {
self.shared.names_map.keys().map(|s| s.as_str())
}
/// Search for a file entry by name
pub fn by_name<'a>(&'a mut self, name: &str) -> ZipResult<ZipFile<'a>> {
Ok(self.by_name_with_optional_password(name, None)?.unwrap())
}
但是用以下的方式就没有问题
let path = "/tmp/a.jar";
let jar = File::open(path).unwrap();
let mut zip = ZipArchive::new(jar).unwrap();
for i in 0..zip.len() {
let entry = zip.by_index(i).unwrap();
println!("name: {}, size: {}", entry.name(), entry.size());
}
这里我比较奇怪的是从方法签名上看 len() 和 file_names()都会发生immutable borrow,而后面by_index 和 by_name都会发生mutable borrow。为什么会一个可以通过检查,一个不行。
pub fn len(&self) -> usize {
self.shared.files.len()
}
len函数实际的签名应该是fn len<'a>(&'a self) -> usize 返回值是usize,所以函数调用完成后就不再和借用有关了。所以 immutable borrow 就结束了。
而file_names实际签名是fn file_names<'a>(&'a self) -> impl Iterator<Item = &'a str> {…}返回值的生命周期和 入参的 immutable ref周期相同,所以后续就检测出同时存在可变和不可变引用了。
详细解释: https://users.rust-lang.org/t/borrow-check-understanding/94260/2
命令行频繁被Killed问题
问题现象是当使用cargo build打包出binary后,通过cp 到 /tmp/jcd执行 会出现 Killed的情况,不是必现,但是当出现之后后续就一直会这样,百思不得其解。
$ /tmp/jcd
[1] 16957 killed /tmp/jcd
后通过在rust user 论坛提问找到答案,不得不说回复效率很高。
https://users.rust-lang.org/t/rust-command-line-tools-keeps-beeing-killed/94179
原因应该是和苹果电脑上的 Code sign机制有关,在苹果没有解决这个问题之前,建议通过ditto替代cp命令来copy程序。
经过检查系统日志确实有出现 Code Signature Invalid的报错

相同的Class CRC和MD5却不一样
问题是发现在集成这个工具到内部的插件框架中,集成过程中发现一个Jar包被另一个module依赖,经过shade插件打包(没有对相关class进行relocate) 后,生成的class crc32不同,被识别为会冲突的类。通过javap -v 查看两个class对比发现里面的仅仅是一些constant pool 不同。
那么怀疑就是maven-shade-plugin 做了什么操作,翻阅了下代码,查看了shade的处理流程.
看到以下这段,发现这不就是我遇到的问题么。

查阅了相应的issue: https://issues.apache.org/jira/browse/MSHADE-391
在3.3.0 才解决,而我使用的版本正好是3.2.4。升级插件重新生成校验码一致了。
解决冲突的Class
最后再回到最初的目的,当我们通过工具检测出冲突的class应该怎么解决呢。
首先我们需要判断这个class是否是运行时所需要的。
如果不是所需要的那么我们就应该直接排掉他,排除有两种手段(这里针对的是maven shade的打包方式),如果在dependency tree中可以看到相应package的依赖,那么可以直接通过如下的白名单 include 或者 exclude 掉某个 artifact。
<artifactSet combine.self="override">
<includes>
<include>commons-dbcp:commons-dbcp</include>
<include>commons-pool:commons-pool</include>
<include>mysql:mysql-connector-java</include>
</includes>
</artifactSet>
但是不排除这个依赖包本身就是fatjar,那么直接通过这种方式就排不掉这个依赖,可以通过filters 配置文件 粒度的匹配过滤
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
<exclude>javax/**</exclude>
<exclude>org/apache/flink/fnexecution/**</exclude>
<exclde>org/slf4j/**</exclde>
</excludes>
</filter>
</filters>
如果这个冲突的class是运行时需要的,那么可以通过relocation的方式给各自的插件包中shade成带特殊前缀的class名,解决同名冲突。
<relocation>
<pattern>org.apache.http</pattern>
<shadedPattern>com.alipay.flink.sls.shaded.org.apache.http</shadedPattern>
</relocation>
用rust 写一个jar包 class冲突检测工具的更多相关文章
- Maven将代码及依赖打成一个Jar包的方式
Maven可以使用mvn package指令对项目进行打包,如果使用java -jar xxx.jar执行运行jar文件,会出现"no main manifest attribute, in ...
- Jmeter用BeanShell Sampler调用java写的jar包进行MD5加密
[前言] 在工作中,有时候我们请求的参数可能需要加密,比如登录接口中的密码做了加密操作,今天我就给大家介绍一种方法:Jmeter用BeanShell Sampler调用java写的jar包进行MD5加 ...
- 将多个jar包重新打包成一个jar包
我介绍的方法是使用java命令来操作的,所以首先的安装jdk,这个就自己搞定吧. 提取jar包 为了将多个jar包打包成一个jar包,首先要将每个jar包的内容提取出来放到一个文件夹下,具体的操作命令 ...
- 一个jar包冲突引起的StackOverflowError
项目运行中错误信息:java.lang.IllegalStateException: Unable to complete the scan for annotations for web appli ...
- maven将项目及第三方jar打成一个jar包
pom.xml中添加如下配置 把依赖包和自己项目的文件打包如同一个jar包(这种方式对spring的项目不支持) <build> <plugins> <plugin> ...
- maven 引用另一个jar包 需要先打包在仓库里面 并在pom里面配置 才可以引用
maven 引用另一个jar包 需要先打包在仓库里面 并在pom里面配置 才可以引用
- 从零开始写一个npm包及上传
最近刚好自己需要写公有npm包及上传,虽然百度上资料都能找到,但是都是比较零零碎碎的,个人就来整理下,如何从零开始写一个npm包及上传. 该篇文件只记录一个大概的流程,一些细节没有记录. tips: ...
- 多个module实体类集合打一个jar包并上传至远程库
本章内容主要分享多个module中的实体类集合生成到一个jar包中,并且发布到远程库:这里采用maven-assembly-plugin插件的功能来操作打包,内容不长却贴近实战切值得拥有,主要节点内容 ...
- 从零开始写一个npm包,一键生成react组件(偷懒==提高效率)
前言 最近写项目开发新模块的时候,每次写新模块的时候需要创建一个组件的时候(包含组件css,index.js,组件js),就只能会拷贝其他组件修改名称 ,但是写了1-2个后发现效率太低了,而且极容易出 ...
- 创建Jmeter中使用的jar包中的工具类方法
1. 在IDEA中新建一个maven工程. 2. 编写工具类方法,如加密算法.此处以加法为例. package DemoTest; public class DemoClass{ public int ...
随机推荐
- odoo 开发入门教程系列-安全-简介
安全-简介 前一章中我们已经创建了第一个打算用于存储业务数据的表.在odoo这样的一个商业应用中,第一个考虑的问题就是谁(Odoo 用户(或者组用户))可以访问数据.odoo为指定用户组用户提供了一个 ...
- Rancher 系列文章-K3s Traefik MiddleWare 报错-Failed to create middleware keys
概述 书接上回:<Rancher 系列文章-K3S 集群升级>, 我们提到:通过一键脚本升级 K3S 集群有报错. 接下来开始进行 Traefik 报错的分析和修复, 问题是: 所有 Tr ...
- 剑指 offer 第 24 天
第 24 天 数学(中等) 剑指 Offer 14- I. 剪绳子 给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m.n都是整数,n>1并且m>1),每段绳子的长度记为 k[ ...
- 第三部分:Spdlog 日志库的实现原理
! https://zhuanlan.zhihu.com/p/617432495 Spdlog 是一个快速.异步的 C++ 日志库,被广泛应用于 C++ 项目中.在这篇文章中,我们将探讨 Spdlog ...
- 如何单机部署多个 MySQL 8.0 实例 ?
在服务器资源有限的情况下,可利用该方案快速搭建各类 mysql 架构方案.各 MySQL 实例共享一个 mysqld 主程序,但各实例数据目录是独立的,存放在不同的文件夹中:好了.废话不多说,直接上干 ...
- kafka rebalance你真的了解吗
介绍 今天主要分享一下 kafka 的 rebalance,在 kafka 中,rebalance 是一个十分重要的概念,很多时候引发的一些问题可能都是由于 rebalance 引起的,rebalan ...
- WPF 界面布局、常用控件入门教程实例 WPF入门学习控件快速教程例子 WPF上位机、工控串口通信经典入门
WPF(Windows Presentation Foundation)是一种用于创建 Windows 桌面应用程序的框架,它提供了丰富的控件库和灵活的界面布局,可以创建现代化的用户界面.下面是 WP ...
- github打不开或者打开慢方法
github最近打不开,很久之前遇到过,但是忘记怎么解决了,查找相关资料后,今天记录在此,以备不时之需. 记住3个关键网址 github网址查询:The world's leading softwar ...
- Java设计模式 —— 面向对象设计原则
1 设计模式概述 1.1 设计模式的定义与分类 设计模式的定义 Design patterns are descriptions of communicating objects and classe ...
- Gradio入门到进阶全网最详细教程[一]:快速搭建AI算法可视化部署演示(侧重项目搭建和案例分享)
Gradio入门到进阶全网最详细教程[一]:快速搭建AI算法可视化部署演示(侧重项目搭建和案例分享) 常用的两款AI可视化交互应用比较: Gradio Gradio的优势在于易用性,代码结构相比Str ...