用rust?寫一個jar包?class沖突檢測工具
Rust很適合寫命令行工具,特別是使用clap
crate 更加方便,這篇文章介紹使用rust寫一個jar包class沖突檢測的工具。項目地址: https://github.com/Aitozi/jar_conflict_detector
首先jar包class沖突的現(xiàn)象是多個jar包中有同名的class,并且class的md5還不一樣,那么就意味著該class存在多個版本,那么就存在沖突的可能。
思路比較簡單,就是遍歷每個jar包,記錄ClassName 和 對應 CRC 校驗碼 及 jar 包的對應關(guān)系。
通過clap
的derive api就可以快速定義個命令行的參數(shù)解析器。
#[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
的元數(shù)據(jù),這樣可以避免讀取原始數(shù)據(jù)生成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
然后根據(jù)文件名獲取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()
都會發(fā)生immutable borrow,而后面by_index
和 by_name
都會發(fā)生mutable borrow。為什么會一個可以通過檢查,一個不行。
pub fn len(&self) -> usize { self.shared.files.len() }
len
函數(shù)實際的簽名應該是fn len<'a>(&'a self) -> usize
返回值是usize,所以函數(shù)調(diào)用完成后就不再和借用有關(guān)了。所以 immutable borrow 就結(jié)束了。
而file_names
實際簽名是fn file_names<'a>(&'a self) -> impl Iterator<Item = &'a str> {…}
返回值的生命周期和 入?yún)⒌?immutable ref周期相同,所以后續(xù)就檢測出同時存在可變和不可變引用了。
詳細解釋: https://users.rust-lang.org/t/borrow-check-understanding/94260/2
命令行頻繁被Killed問題
問題現(xiàn)象是當使用cargo build
打包出binary后,通過cp 到 /tmp/jcd
執(zhí)行 會出現(xiàn) Killed的情況,不是必現(xiàn),但是當出現(xiàn)之后后續(xù)就一直會這樣,百思不得其解。
$ /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機制有關(guān),在蘋果沒有解決這個問題之前,建議通過ditto
替代cp
命令來copy程序。
經(jīng)過檢查系統(tǒng)日志確實有出現(xiàn) Code Signature Invalid
的報錯
相同的Class CRC和MD5卻不一樣
問題是發(fā)現(xiàn)在集成這個工具到內(nèi)部的插件框架中,集成過程中發(fā)現(xiàn)一個Jar包被另一個module依賴,經(jīng)過shade插件打包(沒有對相關(guān)class進行relocate) 后,生成的class crc32不同,被識別為會沖突的類。通過javap -v
查看兩個class對比發(fā)現(xiàn)里面的僅僅是一些constant pool 不同。
那么懷疑就是maven-shade-plugin 做了什么操作,翻閱了下代碼,查看了shade的處理流程.
看到以下這段,發(fā)現(xiàn)這不就是我遇到的問題么。
查閱了相應的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>
到此這篇關(guān)于用rust 寫一個jar包 class沖突檢測工具 的文章就介紹到這了,更多相關(guān)rust 寫jar包 class沖突檢測工具 內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Rust 利用 chrono 庫實現(xiàn)日期和字符串互相轉(zhuǎn)換的示例
在Rust中,chrono庫提供了強大的日期和時間處理功能,使得日期與字符串之間的轉(zhuǎn)換變得簡單,本文介紹了如何在Rust中使用chrono庫將日期轉(zhuǎn)換成字符串,以及如何將字符串解析為日期,對于需要進行日期時間格式化、解析或進行時區(qū)處理的開發(fā)者來說,chrono庫是一個不可或缺的工具2024-11-11Windows系統(tǒng)下安裝Rust環(huán)境超詳細教程
這篇文章主要介紹了如何在Windows系統(tǒng)上安裝mingw64和Rust,mingw64是一個輕便的C語言編譯環(huán)境,可以替代Rust默認使用的Visual?Studio,文中通過圖文介紹的非常詳細,需要的朋友可以參考下2025-02-02詳解Rust中三種循環(huán)(loop,while,for)的使用
我們常常需要重復執(zhí)行同一段代碼,針對這種場景,Rust?提供了多種循環(huán)(loop)工具。一個循環(huán)會執(zhí)行循環(huán)體中的代碼直到結(jié)尾,并緊接著回到開頭繼續(xù)執(zhí)行。而?Rust?提供了?3?種循環(huán):loop、while?和?for,下面逐一講解2022-09-09深入探究在Rust中函數(shù)、方法和關(guān)聯(lián)函數(shù)有什么區(qū)別
在 Rust 中,函數(shù)、方法和關(guān)聯(lián)函數(shù)都是用來封裝行為的,它們之間的區(qū)別主要在于它們的定義和調(diào)用方式,本文將通過一個簡單的rust代碼示例來給大家講講Rust中函數(shù)、方法和關(guān)聯(lián)函數(shù)區(qū)別,需要的朋友可以參考下2023-08-08詳解rust?自動化測試、迭代器與閉包、智能指針、無畏并發(fā)
這篇文章主要介紹了rust?自動化測試、迭代器與閉包、智能指針、無畏并發(fā),本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-11-11