接手过一个老项目,情况是这样:代码仓库里只剩下一堆jar包,源码彻底丢失,文档为零,唯一的线索是部署文档里那句“依赖关系见图”然后图也是坏的。我当时的第一反应是“完蛋了”,第二反应是“总不能把整个项目推翻重写吧”,于是硬着头皮开始研究批量反编译jar这条路。这篇文章不是什么理论科普,是我真实跑完整个流程之后沉淀下来的方法和坑,适合手里攥着几十个jar、需要快速恢复源码或做代码审计的人参考。
1. 哪些场景逼着你必须走“批量反编译”这条路
1.1 你手里只剩一堆jar,其他什么都没留下
先说说我遇到的实际场景。那个老项目的代码交付方式是“编译后交付”,也就是说甲方拿到的、我们拿到的,全都是编译好的class文件打包成的jar,源码压根没有进到交付物里。等到接手方想改功能、修bug、做二次开发的时候,翻遍所有交接材料,只有jar、配置文件、启动脚本和一份已经过时的部署手册。
这其实不是个例。很多外包项目、离职交接不完整的项目、或者历史遗留系统,都会出现“只有jar没有源码”的情况。最典型的就是你依赖了某个二方包,对方只把jar丢给你,出了问题时你连报错堆栈对应的源码都看不到,只能对着反编译出来的代码一行行猜逻辑。
1.2 二方包源码缺失与代码审计的真实需求
另一个常见场景是安全审计。不管是等保测评还是内部安全自查,都会要求对引入的第三方jar、二方jar做代码层面的审计,确认没有后门、没有敏感信息硬编码、没有高危漏洞。这时候你不可能去问每个依赖方要源码(大部分也根本要不到),反编译就成了唯一可行的路径。
而之所以强调“批量”,是因为实际项目中依赖的jar数量往往远超预期。一个小型Spring Boot应用,光是启动时加载的jar就能有几十个;一个微服务体系下的某个服务,依赖jar上百个都很正常。一个一个手动双击打开再导出源码,效率低到让人崩溃,而且容易漏。
1.3 单jar反编译和批量处理的本质区别
单jar反编译的工具和教程一大堆,JD-GUI、Luyten、Bytecode Viewer随便选一个,打开jar看一眼反编译结果,觉得“学到了”。但批量反编译是完全不同的玩法:
- 你必须优先考虑命令行工具,而不是GUI工具,因为GUI工具没法通过脚本循环处理几十上百个jar。
- 你必须处理输出目录的结构设计,否则反编译出来的java文件会全部堆在一起,乱到无法整理。
- 你必须处理失败重试和日志记录,某个jar反编译失败时不能中断整个批次,还得准确知道哪个失败了、为什么失败。
- 你还得面对反编译结果的编译问题,jar里可能依赖了外部库,反编译出的源码直接放回IDE编译时缺这个少那个,需要额外处理。
所以批量反编译不是简单地把“单个反编译”重复执行N遍,它是一套流程,从工具选型、环境准备、脚本编写到源码验证,每一步都有讲究。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 反编译工具怎么选:命令行能力才是批量的分水岭
2.1 主流反编译工具的真实表现对比
先说结论:Java生态下的反编译工具我实际用过不少,包括JD-GUI、JD-CLI、Luyten、Procyon、CFR、Fernflower(IDEA内置反编译器),做单文件反编译时它们各有千秋,但做批量处理时差距一下就拉开了。
| 工具 | 命令行支持 | 批量友好度 | 反编译质量 | 活跃维护 | 备注 |
|---|---|---|---|---|---|
| JD-GUI | 无官方CLI | 差 | 中上 | 已基本停更 | GUI工具,手动操作还行,批量得靠第三方封装 |
| JD-CLI | 有 | 中 | 中上 | 低 | JD-GUI的纯命令行版本,但批量处理时容易出问题 |
| Procyon | 有 | 中 | 中上 | 中 | 对泛型和枚举处理不错,但项目活跃度一般 |
| CFR | 有 | 高 | 强 | 高 | 反编译质量是最好的那一档,对Java新版本语法支持好 |
| Fernflower | 有 | 高 | 强 | 高 | IDEA内置,单独的命令行包可以下载,处理大型jar时表现稳定 |
这里要特别提一下,JetBrains提供了Fernflower的独立命令行版本,就是IDEA里内置的那个反编译器。它的优势在于处理大型jar时不会像JD-GUI那样动不动OOM,而且反编译出的Java代码在大多数情况下能直接放回IDE里编译通过。缺点是它的命令行参数有点“邪门”,不是标准的那种-style语法,得仔细看官方文档或者直接看源码里的参数定义。
2.2 为什么我把CFR和Fernflower列为批量首选
我个人批量处理首选CFR,备选Fernflower。原因有三:
第一,CFR对Java 8以上版本的语法支持非常好。现在很多老项目的jar虽然是旧代码,但class文件版本可能已经是Java 11甚至Java 17,如果反编译器不支持新的class文件格式,直接报错或者输出一堆乱码。CFR在这方面跟进很快,实测下来Java 8到Java 17的class文件都能处理。
第二,CFR的命令行参数设计对脚本调用非常友好。它支持通过--outputdir指定输出目录,支持通过--extraclasspath追加classpath来处理依赖,还支持--silent模式减少日志输出。这些特性就是为批量处理准备的。
第三,CFR处理泛型和内部类的还原度很高。批量反编译几十上百个jar时,你不可能每个都人工比对反编译结果,只能肉眼抽查关键类。CFR还原出的泛型签名、内部类关系、匿名内部类结构,比早期版本的反编译工具靠谱很多,这直接决定了后续读代码和改代码的效率。
2.3 工具组合使用的策略
有一个经验我后来觉得很值钱:不要迷信单一工具,批量处理时要建立“主工具+副工具”的组合策略。
我的实际做法是:先用CFR跑全量批量,把能反编译的jar全部处理掉;然后把CFR报错、反编译结果明显异常的jar挑出来,用Fernflower单独处理一遍;如果Fernflower也搞不定,再用Procyon补一次。几个工具的结果可以交叉比对,哪个结果更接近预期的业务逻辑就用哪个。
为什么这么折腾?因为反编译器本质上是在“猜”编译器当初做了什么优化,不同的工具对同一段字节码的还原策略不同。我遇到过同一个jar在CFR下反编译出的代码有大量var1、var2这类无意义变量名,换Fernflower却还原出了原始的参数名和局部变量名(因为调试信息在class文件的LocalVariableTable里被保留得很好)。这种时候组合使用工具就能大幅提高反编译质量。
3. 批量反编译前,先把环境这层地基打牢
3.1 JDK版本匹配:比你想象中更容易翻车
批量反编译之前,先确认两件事:你自己的JDK版本,以及目标jar的class文件版本。
反编译工具本身需要跑在JDK上,这个没问题。但如果你要反编译的jar是用高版本JDK编译的,比如Java 17的class文件,而你的CFR版本太低,它可能直接报一个"Unsupported class file major version 61"类似错误,或者干脆反编译出完全错乱的代码。所以先把CFR更新到最新版。
我踩过的一个实际坑是:项目环境的JDK是1.8,而反编译工具CFR的某些新版本要求JDK 11以上才能运行。这就出现了一个很尴尬的局面——本地只有JDK 8,CFR新版本跑不起来,旧版本又处理不了高版本的class文件。最后的解决方案是装了一个JDK 11作为独立的运行时,通过环境变量切换给CFR用,而不是影响项目本身的JDK版本。
3.2 输入输出目录结构的设计逻辑
这是批量反编译的“隐藏关卡”。如果直接把所有jar的输出都扔到同一个目录,反编译出来的java文件会按照包名层层建目录,但不同jar里如果有相同的包名和类名(这种冲突在依赖传递中非常常见),就会互相覆盖,而且你根本不知道被覆盖的是谁的源码。
我最终采用的结构是这样的:
bash复制# 所有待反编译的jar统一放在这个目录下,保留原始文件名
input_jars/
business-core-1.0.jar
common-utils-2.1.jar
spring-boot-starter-web-2.5.15.jar
# 反编译输出目录,每个jar一个独立的子目录,目录名与jar文件名保持一致
decompiled_src/
business-core-1.0.jar/
common-utils-2.1.jar/
...
这样做的好处是:哪个jar反编译失败一目了然,也不会发生源码覆盖的混乱。后续如果要按jar维度去恢复Maven工程,也容易对应上。
3.3 日志与失败记录:批量任务的保命项
批量处理最怕的是“闷头跑完,发现一半jar没反编译成功,但不知道是哪些、为什么失败”。所以脚本里必须有完整的成功/失败清单。我写脚本时的习惯是:每个jar处理完成之后,立刻把jar名、状态、耗时、错误信息追加到一个日志文件里,最后再汇总一个失败列表。
这个失败列表太重要了。有一次批量跑了两百多个jar,跑完看了下失败清单,发现有三四个老版本的二方包在CFR下一直报错,原因是这些jar里带了签名信息的inner class,CFR在解析时触发了异常。如果没有失败清单,我根本不知道这几个jar没成功反编译,后面代码审计时就会漏掉这几个关键模块。
4. 从单条命令到全量批量脚本的完整实现
4.1 CFR命令行核心参数逐个说明
CFR的基本调用方式是:
bash复制java -jar cfr.jar <待反编译的jar或class文件> [参数]
对于批量处理来说,最核心的参数是这几个:
| 参数 | 作用 | 我的推荐值 |
|---|---|---|
--outputdir <目录> |
指定反编译结果输出目录 | 必填,并按jar名建子目录 |
--extraclasspath <jar或目录> |
追加依赖classpath,提高反编译准确度 | 非必填,但遇到依赖引用时可显著提高质量 |
--silent true |
关闭不必要的日志输出 | 批量处理时建议开启 |
--caseinsensitivefs true |
处理某些特殊文件系统大小写不敏感的问题 | 视平台而定 |
--removeboilerplate true |
移除反编译代码里的样板代码 | 用于增强可读性 |
--comments false |
不生成反编译注释 | 日志会少一些,我习惯关掉 |
--analysefalse |
对布尔值的分析更激进 | 有时候能改善if条件还原效果 |
这里重点说一下--extraclasspath。当你反编译的jar依赖了另一个jar时,CFR如果能在classpath里找到依赖类,它反编译出的代码会更精确,比如泛型类型、方法重载、继承关系都会还原得更准。批量反编译时,我通常会先把所有待处理jar的路径拼成一个classpath字符串,然后作为--extraclasspath传给CFR。这样虽然处理时间变长了,但反编译质量确实明显提高。
4.2 Windows/Linux通用的批量反编译脚本
下面这个脚本是我实际在用的,兼顾了Windows(Git Bash环境)和Linux/Mac。核心逻辑很简单:遍历指定目录下所有.jar文件,对每个文件调用CFR,输出到以jar文件名命名的子目录,记录成功失败日志。
bash复制#!/bin/bash
# 批量反编译脚本
# 用法: ./batch_decompile.sh <输入目录> <输出目录>
INPUT_DIR="${1:-./input_jars}"
OUTPUT_DIR="${2:-./decompiled_src}"
CFR_JAR="/path/to/cfr.jar" # 修改为你的CFR路径
LOG_FILE="${OUTPUT_DIR}/decompile.log"
FAIL_FILE="${OUTPUT_DIR}/failed.log"
mkdir -p "${OUTPUT_DIR}"
rm -f "${LOG_FILE}" "${FAIL_FILE}"
total=0
success=0
failed=0
for jar_file in "${INPUT_DIR}"/*.jar; do
[ -e "${jar_file}" ] || continue
total=$((total + 1))
jar_name=$(basename "${jar_file}" .jar)
out_dir="${OUTPUT_DIR}/${jar_name}.jar"
# 跳过已经输出过且非空的目录,方便断点续跑
if [ -d "${out_dir}" ] && [ "$(ls -A "${out_dir}")" ]; then
echo "[SKIP] ${jar_name} (already exists)" | tee -a "${LOG_FILE}"
continue
fi
mkdir -p "${out_dir}"
# 如果希望把依赖全部喂给CFR,可以在这里拼classpath
# EXTRA_CP=$(ls "${INPUT_DIR}"/*.jar | tr '\n' ':')
# 但实测在jar量级大的时候,这个参数会让速度慢很多,我一般在遇到质量问题时才使用
echo "[PROCESS] ${jar_name}" | tee -a "${LOG_FILE}"
java -jar "${CFR_JAR}" "${jar_file}" \
--outputdir "${out_dir}" \
--silent true \
--comments false \
--removeboilerplate true \
2>>"${FAIL_FILE}" && {
success=$((success + 1))
echo "[OK] ${jar_name}" >> "${LOG_FILE}"
} || {
failed=$((failed + 1))
echo "[FAIL] ${jar_name}" >> "${FAIL_FILE}"
}
done
echo "======================================" | tee -a "${LOG_FILE}"
echo "Total: ${total}, Success: ${success}, Failed: ${failed}" | tee -a "${LOG_FILE}"
说几个脚本里的实用细节:
- 断点续跑逻辑。如果某个jar已经反编译过了并且输出目录不为空,直接跳过。这在处理一半脚本中断、或者调整参数后重跑时非常省时间。
- 失败信息单独存文件。
2>>"${FAIL_FILE}"把CFR的异常输出单独保存,方便后面逐个排查。注意这里我用的是追加方式,不会覆盖之前的失败记录。 - jar目录名保留.jar后缀。我特意让输出目录名是
business-core-1.0.jar而不是business-core-1.0,是为了后续处理时更容易匹配到原始jar文件。
4.3 用Java代码驱动反编译的场景
有时候命令行脚本还不够好,尤其是当你的反编译任务不是单纯的“一个jar文件”,而是“一个完整应用的所有依赖”时,你需要的可能不是Shell脚本,而是一个能枚举运行中classpath的Java程序。
比如你想反编译一个正在运行的Spring Boot应用的全部依赖jar,一个更聪明的做法是:先找到应用的PID,然后通过jinfo或/proc/<pid>/maps拿到应用实际加载的jar路径清单(也就是应用运行时的classpath),再对这些jar做批量反编译。这样做比手动收集jar要准得多,因为你拿到的就是应用真正用到的那些jar,而不是某个目录下所有文件。
这里有个从热搜词里翻出来的问题很有代表性:could not find artifact org.csource:fastdfs-client-java:jar:1.27-snapshot。这类问题在反编译老项目时特别常见——你想把反编译结果恢复成可编译的Maven工程,结果Maven Central上根本没有这个依赖,而你本地的jar包里恰好有这个类。这时候你要走的路径不是去问“这个依赖在哪”,而是直接把你手里的那个jar反编译了,自己把缺失的类补回去。
用Java代码驱动反编译的真实例子(比如用CFR的API去反编译一个类):
java复制import org.benf.cfr.reader.api.CfrDriver;
import java.util.*;
public class DecompileWithCfr {
public static void main(String[] args) {
Map<String, String> options = new HashMap<>();
options.put("outputdir", "/tmp/decompiled");
options.put("silent", "true");
options.put("comments", "false");
CfrDriver driver = new CfrDriver.Builder()
.withOptions(options)
.build();
List<String> path = Collections.singletonList("/path/to/your.jar");
driver.analyse(path);
}
}
这种方式可以把CFR作为库集成到自己的工具链里,适合做后续的自动化处理。不过说实话,如果能用命令行脚本解决,没必要引进代码依赖,命令行简单直接,出问题也好排查。
5. 反编译的最后一公里:源码验证与依赖恢复
5.1 反编译结果的第一轮体检
批量反编译跑完之后,先别急着欢呼,因为你拿到的“源码”大概率是不能直接编译通过的。第一轮体检要做这几件事:
-
抽查关键类的反编译质量。优先看你们项目核心业务逻辑的类,比如包含复杂if-else、循环、异常处理的类。如果这些类的反编译结果读起来通顺,变量名能对上业务含义,那整体质量应该不会差。如果关键类反编译出来全是
var0、var1这种无意义变量名,或者逻辑上出现了“反编译器都能看出来错了”的代码,那就要考虑换工具单独处理这些jar。 -
检查资源文件是否完整。jar里通常不只是class文件,还有配置文件(
application.yml、META-INF/spring.factories)、静态资源、mybatis的XML映射文件等。反编译工具一般不动非class文件,但有时候你指定的输出目录权限、文件系统大小写问题会导致资源文件丢失或错位。我遇到过反编译后XML文件还在、但目录层级不对,导致后续扫描找不到映射文件的情况,所以这步要亲手确认。 -
对比jar文件中的类清单与反编译产物。写一个简单的脚本,用
jar tf列出jar里的.class文件列表,再对照输出目录下.java文件的类名清单,确保没有丢类。有些反编译工具在遇到异常时会跳过某个类,如果没有对照清单,你根本不知道哪个类没被反编译出来。
5.2 把散落源码拼回可编译的Maven工程
这是整个批量反编译中最耗时间,也最需要经验的一步。反编译出的.java文件要恢复成一个能跑的Maven工程,需要做到:
第一步:按jar维度整理源码树。之前设计的decompiled_src/<jar名>这个结构在这里就发挥作用了——你可以针对每个关键jar,把反编译源码和它的包名结构对应起来,放入Maven工程的src/main/java目录。
第二步:识别源码依赖关系。通过import语句和包的对应关系,整理出类之间的依赖图谱。这里我给一个非常实用的建议:不要手动一个个类去查依赖,直接用Maven的依赖管理机制。先在pom.xml里声明你在反编译中识别出的第三方依赖(从import语句里能看出用了哪些第三方包,比如org.springframework、com.fasterxml.jackson、org.apache.commons等),让Maven自动拉取。你的反编译源码里的类缺失如果是因为缺少第三方依赖,Maven会编译报错,你再根据报错提示补充对应依赖即可。
第三步:处理“幽灵依赖”。就是热搜里那个could not find artifact org.csource:fastdfs-client-java:jar:1.27-snapshot。这类包在公共仓库里不存在,只在老项目的某个私有仓库里,甚至只在jar包里。处理方式有两种:
- 如果这个依赖的jar你已经有了,用
mvn install:install-file -Dfile=xxx.jar -DgroupId=org.csource -DartifactId=fastdfs-client-java -Dversion=1.27-snapshot -Dpackaging=jar把它安装到本地仓库。 - 如果连jar都没有,但你反编译代码里确实引用了这些类,那就要从其他途径找回jar——这本身就是另一个需要解决的项目级问题,不是反编译能解决的。
第四步:解决编译报错的代码级问题。反编译源码在编译时最常见的几类报错是:泛型擦除导致的类型不匹配、final static变量缺少初始化、内部类的构造函数访问权限问题、enum相关构造问题。对于这些报错,不要追求反编译源码和原始版本一字不差,重点是逻辑等价。你可以手动改这部分代码,让它们符合Java编译器要求,同时保证行为逻辑不变。
5.3 从反编译源码恢复一个具体业务的思路
拿一个具体的例子来说明怎么走通这个流程。假设你要恢复的jar是一个老版本的Spring Boot服务,反编译之后发现它内部的Controller、Service、Mapper结构都还在,但代码里引用了org.csource.fastdfs下的类(就是那个fastdfs-client-java),而你手里没有这个jar,Maven仓库也没有。
正确的做法是:
- 先搜索代码中所有
org.csource.fastdfs的引用点,了解它用到了哪些类、哪些方法。 - 找到你手头有的fastdfs-client-java jar(如果完全没有,就要考虑是否用别的方式实现同等功能,或者去二手渠道找这个历史包)。
- 用
mvn install:install-file安装到本地仓库,并在pom.xml里声明依赖。 - 重新编译,解决剩余的报错。
这里的重点在于:不要试图把所有缺失的依赖都从反编译代码中“推理”出来,而是先让编译报错替你发现缺失,再逐个补充。 这个方法效率最高,也最不容易遗漏。
6. 批量反编译路上的坑:我踩过的那些,以及完整的排查链路
6.1 jar包损坏与manifest缺失
先说一个非常典型的报错:error opening zip file or jar manifest missing : some-jar.jar error occurre。这个报错信息看着吓人,其实核心就两个可能:要么jar文件本身就是坏的,要么jar的META-INF/MANIFEST.MF缺失或损坏。
有一次我批量跑反编译,中间有个jar报了这个错。我的排查链路是:
- 先用
jar tf 文件名.jar看能否列出jar内容。如果这个命令直接报错,基本确定是文件损坏。 - 再用
unzip -t 文件名.jar检查zip完整性,定位具体是哪个文件损坏。 - 如果是文件损坏,回到源仓库或同事的机器上重新拉取这个jar,对比文件MD5确认是否传输过程中损坏。
- 如果jar没坏,但确实没有
MANIFEST.MF文件,这个jar其实还是可以被CFR反编译的——只不过有些依赖反射、SPI的服务发现机制的应用在运行时可能出问题。对于反编译本身来说,manifest缺失不影响class文件解析。
这个坑提醒我们:批量任务里的“失败清单”一定要保留完整的错误信息,直接报“ERROR”两个字是没有排障价值的。
6.2 运行期报错和反编译代码的关联排查
热搜里有个很典型的报错:error creating bean with name 'memorymonitor' defined in url [jar:file:/volu...]。这种报错在恢复反编译工程后启动时也经常出现,原因往往是:反编译恢复的工程在Spring容器初始化某个Bean时,因为类路径不对、依赖Bean缺失或配置项不正确导致创建失败。
排查思路是这样的:
- 从报错堆栈里的
jar:file:路径找到对应的jar。 - 用我们之前做的项目源码树,找到
memorymonitor这个Bean的定义在哪个类里。 - 去反编译源码中看这个类依赖了哪些其他Bean、哪些配置项。
- 对照原始启动脚本、原始配置文件,确认缺失的配置或Bean。
- 如果反编译源码里这个类的构造逻辑有异常(比如对某个方法返回值的处理明显不对),回到相应jar重新反编译或手动修正。
这里我想强调一个判断点:反编译后的代码逻辑如果出现明显异常,不要第一反应就是“代码本来就有问题”。 很可能是反编译器对这个类的还原出现了偏差。这时你就要拿反编译结果和字节码层面比对——用javap -c查看原始字节码的执行逻辑,再和反编译源码对比判断。这种精细活虽然耗时,但遇到关键核心类时是值得的。
6.3 内部类与Lambda表达式的还原问题
内部类是反编译重灾区。CFR在处理内部类时有时候会生成额外的Class$1、Class$Inner这种文件,而原始的内部类名可能已经通过InnerClasses属性保留在字节码里。反编译后的源码放到IDE里编译时,匿名内部类、lambda表达式的处理经常会产生“duplicate class”或“cannot find symbol”的编译错误。
我的处理经验是:
- 优先查看反编译工具有没有生成
.java文件之外的附加说明文件(CFR有时候会生成.txt描述文件,说明它做了哪些调整)。 - 对于lambda表达式,Java 8之后编译生成的
invokedynamic指令+LambdaMetafactory,反编译器一般能还原成lambda写法,但偶尔会因为泛型信息不完整还原成匿名内部类。这个不影响逻辑正确性,我可以接受。 - 对于内部类,注意检查反编译出的
Outer.java里是否声明了Inner类的引用,以及Inner.java里是否有正确的Outer类引用。不对的话手动调整。
6.4 文件系统大小写和目录层级导致的资源丢失
还有一个特别隐蔽的坑:反编译输出的文件在Linux下正常,同步到Windows之后就找不到某些xml或配置文件了,原因就是jar里同时存在ApplicationConfig.xml和applicationconfig.xml这种大小写不敏感导致冲突的文件名。Windows文件系统不区分大小写,后写的文件直接覆盖先写的。
针对这类问题,我在批量反编译时会在脚本里设置--caseinsensitivefs true参数,让CFR在输出时主动处理一下,避免输出文件中出现大小写冲突的类/资源名。虽然这个参数并非万能(它主要是让CFR在输出时对大小写冲突的类名做重命名处理),但能减少不少后续复制文件到Windows时的麻烦。
7. 从“反编译完”到“能真正用上”,还需要补哪些功课
先声明,到这里,批量反编译的核心流程已经走通了,但你手里这份反编译源码,离“可以直接交付”还有几步路。
第一,补注释和文档。反编译出来的源码几乎没有注释和原始设计说明,接手的人(包括未来的你自己)如果只拿到这份代码,理解成本依然很高。我的习惯是,在每个关键模块(比如核心Service、涉及支付的流程、状态机流转)的反编译源码文件头部加上还原说明,记录这个类的主要职责、涉及的业务场景、以及反编译还原过程中做了哪些手动修正。
第二,整理依赖清单和使用到的第三方库。反编译源码恢复成Maven工程后,明确记录你实际用到的依赖和版本,尤其是那些不在中央仓库里的“幽灵依赖”。这些信息对于后续维护和部署是极其宝贵的。
第三,建立与原始jar的对应关系。我强烈建议你保留反编译输入jar、反编译输出源码、重建工程三者之间的映射表。以后哪天原始jar更新了,你只需要重新反编译这个jar,再对比差异即可,不需要全量重跑。
第四,用实际运行来验证。反编译源码恢复的工程,一定要在本地启动起来,用真实的接口调用去验证核心功能是否符合预期。编译通过只代表语法正确,业务逻辑是否等价只能靠运行验证。这一步是绕不开的。
我个人的体感是,批量反编译这项工作,技术层面的难度其实还好,真正的门槛在于耐心和细致——你不光要会写脚本、调参数,还要能在一堆变量名是var1、var2的代码里理出业务逻辑,并且接受“有些细节可能永远无法100%还原”的现实。但只要流程跑通,面对一堆只有jar的遗留系统,你依然能把它从“黑盒”变成“白盒”,这就已经赢了一大半。最后提醒一句,反编译工具虽然强大,但请仅用于你有权处理、有权检查代码的场景,合规永远是第一位的。
