游乐游手机版
首页/编程语言/文章详情

Java解析嵌套jar中class文件过程

时间:2026-04-22 11:05
一、简述 用Ma ven构建项目,最终打包成jar文件时,一个常见的结果是:这个jar包里,不仅包含了你自己的代码,还打包了所有依赖的库文件。这就形成了一个“嵌套”的结构。 那么问题来了,如果想直接从这个“大包”里,解析出嵌套在内部那些依赖jar包中的class文件,该怎么办?别急,下面就来聊聊两种

一、简述

用Ma ven构建项目,最终打包成jar文件时,一个常见的结果是:这个jar包里,不仅包含了你自己的代码,还打包了所有依赖的库文件。这就形成了一个“嵌套”的结构。

Ja va解析嵌套jar中class文件过程

那么问题来了,如果想直接从这个“大包”里,解析出嵌套在内部那些依赖jar包中的class文件,该怎么办?别急,下面就来聊聊两种主流且实用的方法:一种是借助Spring Boot生态里的spring-boot-loader包提供的JarFileArchive;另一种则是使用Ja va标准库里的ja va.util.jar.JarFile。两种方式各有特点,咱们逐一拆解。

二、JarFileArchive方式

1. spring-boot-loader依赖引入

要使用第一种方法,首先得把工具包引入项目。在Ma ven的pom.xml里加上下面这段依赖即可:


    org.springframework.boot
    spring-boot-loader
    2.2.4.RELEASE

注意,版本号可以根据实际情况调整。

2. demo案例

这里有个细节需要提醒:不同版本的spring-boot-loader,其getNestedArchives方法签名可能不同。比如在较早版本中用的方法,在高版本里可能已被标记为废弃,转而使用参数更丰富的新方法。所以,实际编码时最好跟进一下你所使用版本的官方源码,做相应调整。

核心逻辑其实很清晰,看下面的代码示例就明白了:

   public static void main(String args[]) throws Exception {
        String jarPath = "C:\\Users\\root\\Desktop\\make-test.jar";
        // 方案一:spring-boot-loader
        long start1 = System.currentTimeMillis();
        getClassInfoByJarLib(jarPath);
        long end1 = System.currentTimeMillis();
        log.info("收集所有lib类ClassInfo,花费时间={}",(end1-start1));
    } 

    public static void getClassInfoByJarLib(String jarPath) {
        String filePath;
        String separator = File.separator;
        if("\\".equals(separator)){
            // windows系统使用如下路径
            filePath = "file:/"+ URLDecoder.decode(jarPath, StandardCharsets.UTF_8).replaceAll("\\\\","/")+"!/";
        }else if("/".equals(separator)){
            // linux系统使用如下路径
            filePath = "file:"+ URLDecoder.decode(jarPath, StandardCharsets.UTF_8).replaceAll("\\\\","/")+"!/";
        }else {
             throw new RuntimeException("未知操作系统,解析jarLib失败");
        } 
        String rootJarPath = "jar:"+ filePath;
        try {
            JarFileArchive jarFileArchive = new JarFileArchive(new Handler().getRootJarFileFromUrl(new URL(rootJarPath)));
            // getNestedArchives获取嵌套的jar等文件,参数是个EntryFilter,用于过滤
            jarFileArchive.getNestedArchives(entry -> entry.getName().startsWith("BOOT-INF/lib/") && entry.getName().endsWith(".jar"))
                    .forEach(archive -> {
                        archive.iterator().forEachRemaining(entry -> {
                            String entryName = entry.getName();
                            // 过滤出嵌套jar包中的字节码文件
                            if (entryName.endsWith(".class")) {
                                String className = entryName.replace('/', '.').replace(".class", "");
                                log.info("className:{}",className);
                            }
                        });
                    });
        } catch (IOException e) {
            log.error("解析嵌套jarLib中ClassInfo异常,jarPath={}",jarPath,e);
            throw new RuntimeException(e);
        }
    }

这段代码的关键在于,它利用JarFileArchive这个封装好的工具,能够非常方便地遍历出嵌套在BOOT-INF/lib/目录下的所有jar包,并进一步解析出里面的每一个class文件。路径处理和异常捕获也都考虑进去了,开箱即用。

三、JarFile方式

1. demo案例

如果你不想引入额外的依赖,纯用JDK自带的能力行不行?当然可以。Ja va标准库中的JarFile类同样能完成这个任务,只不过需要自己多写几行处理逻辑。

   public static void main(String args[]) throws Exception {
        String jarPath = "C:\\Users\\root\\Desktop\\make-test.jar";
        // 方案二:JarFile
        long start2 = System.currentTimeMillis();
        processJar(jarPath);
        long end2 = System.currentTimeMillis();
        log.info("收集所有lib类ClassInfo,花费时间={}",(end2-start2));
    } 
    private static void processJar(String jarPath){
        try (JarFile jarFile = new JarFile(new File(jarPath))) {
            jarFile.stream().parallel()
                    // 过滤出所有符合要求的jar包
                    .filter(entry -> !entry.isDirectory() && entry.getName().startsWith("BOOT-INF/lib/") && entry.getName().endsWith(".jar"))
                    .forEach(entry -> processNestedJar(jarFile, entry.getName()));
        } catch (IOException e) {
            log.error("解析嵌套jarLib中ClassInfo异常,jarPath={}",jarPath,e);
            throw new RuntimeException(e);
        }
    }

    private static void processNestedJar(JarFile jarFile, String entryName){
        // 处理嵌套jar文件
        try (InputStream nestedJarStream = jarFile.getInputStream(jarFile.getJarEntry(entryName));
            JarInputStream jarInputStream = new JarInputStream(nestedJarStream)) {
            JarEntry nestedEntry;
            while ((nestedEntry = jarInputStream.getNextJarEntry()) != null) {
                if (nestedEntry.isDirectory()) {
                    continue;
                }
                String nestedEntryName = nestedEntry.getName();
                if (!nestedEntryName.endsWith(".class")) {
                    continue;
                }
                try {
                    String className = nestedEntryName.replace('/', '.').replace(".class", "");
                    log.info("className:{}",className);
                } catch (Exception e) {
                    log.error("目标类={}查找失败",nestedEntryName,e);
                    throw new RuntimeException(e);
                }
            }
        } catch (IOException e) {
            log.error("目标类={}查找失败",entryName,e);
            throw new RuntimeException(e);
        }
    }

这种方式的核心是使用JarFileJarInputStream进行两层遍历。第一层找到嵌套的jar包,第二层再像解压缩一样,逐个读取嵌套jar里的class条目。代码量稍多,但胜在零依赖,理解起来也更底层。

四、两种方式对比

光说不练假把式,性能如何还得看实测。我们用一个名为make-test.jar的实际项目进行测试,其中大约包含了200个依赖lib,总计超过7万个class字节码文件。

结果很有意思:

  • 使用JarFileArchive方案,解析全部文件大约耗时 1.5秒
  • 使用标准JarFile方案,完成同样的工作则花了大约 6秒

差距显而易见。这主要是因为spring-boot-loader中的JarFileArchive为这种嵌套场景做了深度优化,封装了更高效的遍历逻辑。而纯JDK方案虽然通用,但在处理大量嵌套条目时,性能开销相对较大。

总结

简单总结一下:如果你的项目已经是Spring Boot体系,或者不介意引入一个轻量级的工具包,那么JarFileArchive无疑是更高效、更优雅的选择。如果你追求极致的“纯净”,或者只是在写一个简单的工具,那么基于标准JarFile的方案也能可靠地完成任务,只是需要多付出一点时间成本。

两种方法的核心思路和代码实现都在上面了,可以根据自己的实际场景灵活选用。希望这份梳理能为大家在处理类似问题时,提供一个清晰的参考。


您可能感兴趣的文章:
  • ja va开发读取嵌套jar包中的文件
  • windows后台运行Ja va jar包实现方式
  • Ja va中-jar命令参数设置的完整指南
  • Ja va JAR包运行与反编译实践
  • ja va -jar启动原理详解(附实操验证和注意事项)
  • mysql-connector-ja va.jar包的下载实践
来源:https://www.jb51.net/program/3625946lx.htm
上一篇如何检查Debian上的Golang版本 下一篇Rust程序在Debian上如何部署
本站内容用于信息整理与展示,如有侵权或内容问题请及时联系处理。

相关推荐

补充同频道和同主题内容,方便继续浏览更多相关内容。

同类最新

继续查看同栏目最近更新的文章。

更多
PyTorch中使用多维索引张量对高维张量批量索引的正确方法
编程语言 · 2026-07-03

PyTorch中使用多维索引张量对高维张量批量索引的正确方法

本文深入讲解如何在 PyTorch 中利用形状为 [b, k] 的索引张量 B,对形状为 [b, m, n] 的高维张量 A 执行高效批量索引,最终得到 [b, k, n] 的输出。核心思路在于合理扩展索引维度并配合 torch gather 实现精准的逐行抽取。 很多人处理高维张量的批量索引时都会

Go中...操作符解包切片传递可变参数函数
编程语言 · 2026-07-03

Go中...操作符解包切片传递可变参数函数

在 Go 语言中,` ` 运算符放在切片变量后面(如 `slice `)的作用是将该切片“展开”为多个独立参数,专门用于调用那些接受可变参数(` T`)的函数,例如 `append` 或 `fmt Println`。这是一种类型安全的语法糖,并非省略号或通配符,能够帮助开发者更简洁地处理

macOS与WSL2下PHP多版本切换失效问题排查与修复指南
编程语言 · 2026-07-03

macOS与WSL2下PHP多版本切换失效问题排查与修复指南

本文深入分析在 macOS 或 WSL2(Ubuntu)开发环境中,通过 Homebrew 管理 PHP 多版本时,php -v 始终显示旧版本(如 php@5 6)的深层原因,并给出系统性解决方案,覆盖 PATH 冲突、符号链接逻辑、Shell 初始化配置、系统残留配置等关键环节。 遇到这种情况的

PHP JSON解析深层嵌套对象属性访问失败的解决方法
编程语言 · 2026-07-03

PHP JSON解析深层嵌套对象属性访问失败的解决方法

使用 json_decode() 解析 API 返回的 JSON 数据时,经常遇到某个子属性无法正常获取,始终返回 NULL —— 这是许多 PHP 开发者都曾碰到过的棘手问题。通常并非数据丢失,而是对象嵌套层级比预期更深,导致访问路径不正确。 举例来说,你看到返回的 JSON 里有一个 appea

nnU-Net v2预处理卡死问题的成因分析与实用解决指南
编程语言 · 2026-07-03

nnU-Net v2预处理卡死问题的成因分析与实用解决指南

> 使用 nnUNetv2_plan_and_preprocess 处理大规模数据集(例如 704 例样本)时,程序常因多进程加载导致死锁而停滞。核心原因在于默认并发数过高引发资源竞争或 I O 阻塞,适当降低并发数即可稳定完成全量预处理。 你在使用 `nnunetv2_plan_and_prepr