Java jdeps 静态依赖分析为什么可能漏掉反射加载
用 jdeps 分析 Java JAR 时,结果里没有某个通过反射加载的类,并不一定是参数写错了。jdeps 读取的是 class 文件、目录或 JAR 中能够静态识别的类级别和包级别依赖;它不会替应用执行一遍,也不会自动读取你的配置中心、插件目录或运行时输入。
因此,Class.forName("com.example.Plugin") 这类字符串类名可能不会像 new Plugin() 那样出现在静态依赖里。正确做法是先把 jdeps 的结论当作“字节码可见依赖清单”,再把反射、服务发现和外部配置列入运行时复核。
官方文档:https://docs.oracle.com/en/java/javase/25/docs/specs/man/jdeps.html
jdeps 能看见什么,不能替你判断什么
Oracle 文档把 jdeps 定义为 Java 类依赖分析器。输入可以是 .class 文件、目录、JAR,或者用于分析的限定类名;输出可以按包或类展示依赖。它的核心依据是编译后字节码里的类型引用和模块信息。
下面几种关系通常属于静态分析的可见范围:
- 方法签名、字段类型、继承关系中直接出现的类。
- 方法体里经过编译器记录为类型引用的构造、静态调用或字段访问。
- 模块描述符中的
requires、服务声明等已经写入模块元数据的关系。
而类名来自字符串、配置文件、数据库、插件目录或远程响应时,真正的加载对象要到运行时才确定。即使反射调用最终成功,静态工具也不应凭空猜测所有可能的类。

用一个最小例子判断“漏掉”是否合理
假设应用通过配置得到插件类名,再用反射创建对象:
public final class PluginLoader {
public static Object load(String className) throws ReflectiveOperationException {
// 类名来自配置或命令行,编译时不固定为某个插件类型。
Class> type = Class.forName(className);
// 统一通过无参构造创建插件实例,失败时交给调用方处理。
return type.getDeclaredConstructor().newInstance();
}
}
这里的 Class、ReflectiveOperationException 等类型本身是静态可见的,但字符串里的 className 可能是任意实现类。若改成下面这种固定类型引用,字节码中会留下明确的类依赖:
public final class BuiltInPlugin {
public static Object create() {
// 直接写入具体类型,编译器会把该类型记录到 class 文件引用中。
return new com.example.plugins.ReportPlugin();
}
}
所以“jdeps 没列出反射目标”首先应解释为分析边界,而不是立即判定工具漏报。需要重点追踪的是:类名从哪里来、允许哪些实现、实现类位于哪个 JAR,以及应用启动时是否真的会走到这条路径。
先用三种输出确认静态清单的粒度
不要一开始就用最短的模块列表决定裁剪运行时。先从可读输出入手,再根据目标选择模块级结果。
# 先看输入 JAR 的模块摘要,确认基础依赖是否已经出现。 jdeps --summary build/app.jar # 再递归查看运行时依赖,便于发现间接引用的库模块。 jdeps --list-deps --recursive build/app.jar # 最后生成可供 jlink --add-modules 使用的逗号分隔模块名。 jdeps --print-module-deps --recursive build/app.jar
--summary 适合先建立整体方向;--list-deps 会列出模块依赖,并在相关情况下展示 JDK 内部 API 包名;--print-module-deps 给出更适合脚本消费的模块名列表。官方说明还特别指出,前两种模块分析默认会涉及传递依赖,而 --no-recursive 可以收窄为非传递分析。
这几种输出回答的是不同问题:
| 目标 | 优先参数 | 得到的结论 |
|---|---|---|
| 快速看模块概览 | --summary | 当前输入与模块之间的概略关系 |
| 阅读依赖细节 | --list-deps | 模块依赖与相关包名 |
| 交给 jlink | --print-module-deps | 逗号分隔的模块名集合 |
| 只看直接层 | --no-recursive | 不继续遍历传递依赖 |
反射场景中最容易混淆的三个边界
固定字符串不是固定依赖清单
如果代码里写死了 Class.forName("com.example.ReportPlugin"),字符串对人是线索,但不应把它等同于编译器记录的类常量引用。能否加载成功,还取决于目标类是否在运行时 class path 或 module path 上、访问权限是否允许,以及初始化过程是否依赖其他资源。
外部配置可能改变候选集合
当类名来自 YAML、环境变量、数据库或服务端下发内容时,静态分析甚至不知道候选集合。此时要从配置样例、默认值、部署目录和发布清单反向整理可能的插件 JAR,不能只看一次 jdeps 输出。
服务发现需要看声明和实现两侧
使用 ServiceLoader 时,模块描述符中的 uses、provides ... with 或传统的 META-INF/services 文件会影响发现过程。模块声明已经表达出来的关系更容易纳入分析,但实现是否被打包、是否在运行时路径上,仍然需要检查发行物本身。
排除参数和路径造成的“假漏报”
并非所有空白结果都是反射问题。输入缺少依赖、路径配置不完整或多版本 JAR 选错版本,也会让静态清单看起来不完整。
# 把应用依赖放入 class path,避免 jdeps 只能看到主 JAR。 jdeps --class-path 'lib/*' --list-deps build/app.jar # 模块化应用要明确提供模块路径,和 class path 分开处理。 jdeps --module-path 'mods/*' --list-deps build/app.jar # 多版本 JAR 需要选择实际运行时对应的版本层。 jdeps --multi-release 17 --list-deps lib/vendor.jar # 仅在你确认可以接受遗漏时忽略缺失依赖,别把它当作修复。 jdeps --ignore-missing-deps --list-deps build/app.jar
如果使用了 --ignore-missing-deps,输出的“能继续”只代表工具不因缺失项停止,不代表运行时依赖已经齐全。更稳妥的顺序是先补齐 class path/module path,确认分析对象与部署时的版本一致,再决定是否需要忽略无法取得的依赖。
把静态结果和运行时入口放在一起复核
当目标是用 jlink 裁剪运行时,或者要判断某个插件 JAR 是否能独立部署,可以建立一张简单的合并清单:
- 保存 jdeps 的
--list-deps阅读结果和--print-module-deps机器可读结果。 - 列出启动参数中的 class path、module path,以及应用实际扫描的插件目录。
- 检查配置文件里的类名、服务声明和每个实现类所在的 JAR。
- 把应用启动入口、可选命令、定时任务和按需加载功能分别标记,避免只覆盖默认启动路径。
- 只有静态清单和运行时入口都覆盖后,才把模块集合用于 jlink 或发布裁剪。

这一步的重点不是让 jdeps 变成动态追踪器,而是承认两类信息的来源不同:字节码提供编译时可见关系,配置和插件机制提供运行时选择。把它们合并成发布清单,才能避免因“静态结果很干净”而误删真实需要的模块。
常见问题
为什么写了 Class.forName,jdeps 仍然没有列出目标类?
因为目标类名可能只是字符串,静态分析不负责枚举所有可能的运行时输入。请检查配置、插件目录和运行时路径,并确认目标类是否会被实际加载。
--print-module-deps 能否替代反射依赖清单?
不能。它适合把已分析到的模块依赖输出成 jlink 可消费的格式,但不会替你补齐外部配置、动态插件或未被静态引用的实现类。
看到缺失依赖时应该先加 --ignore-missing-deps 吗?
不建议把它当第一步。先检查 class path、module path、JAR 版本和输入范围;只有明确知道缺失项不影响当前分析时,才使用该参数继续获得部分结果。
怎样判断是参数问题还是反射边界?
先用完整路径执行一次 --list-deps --recursive,再核对配置和插件目录。如果直接类型引用也缺失,优先检查分析路径;如果只有字符串类名缺失,通常属于动态加载边界。
小结:jdeps 适合回答“class 文件静态引用了哪些依赖”,不适合单独回答“应用运行时可能加载哪些类”。面对反射加载,先用参数和路径排除分析范围问题,再把配置、服务声明、插件目录和所有启动入口纳入同一份复核清单。
PHP soundex 编码相同为什么不表示单词相同
- 上一篇
- PHP soundex 编码相同为什么不表示单词相同
- 下一篇
- Go gofpdf 怎样嵌入UTF8字体输出中文PDF
-
- 文章 · java教程 | 2小时前 |
- Java jlink 运行时镜像为什么不包含完整JDK
- 500浏览 收藏
-
- 文章 · java教程 | 3小时前 | Java ·
- Java jlink 怎样只打包应用需要的模块
- 353浏览 收藏
-
- 文章 · java教程 | 8小时前 |
- Java PropertyChangeSupport 新旧值相等时为什么可能不触发通知
- 408浏览 收藏
-
- 文章 · java教程 | 8小时前 |
- Java PropertyChangeSupport 怎样只监听指定属性
- 420浏览 收藏
-
- 文章 · java教程 | 9小时前 | java Introspector stopClass JavaBeans
- Java Introspector 的 stopClass 怎样排除继承属性
- 145浏览 收藏
-
- 文章 · java教程 | 9小时前 |
- Java Introspector 怎样取得 JavaBean 的属性描述
- 447浏览 收藏
-
- 文章 · java教程 | 10小时前 |
- Java ImageReader 为什么需要同时处理输入流关闭和 dispose
- 176浏览 收藏
-
- 文章 · java教程 | 10小时前 |
- Java ImageReader 怎样只读取图片尺寸而不解码整图
- 273浏览 收藏
-
- 文章 · java教程 | 11小时前 |
- Java PrintService 怎样查询打印属性的支持范围
- 340浏览 收藏
-
- 文章 · java教程 | 11小时前 |
- Java PrintService 怎样确认打印机接受的文档类型
- 114浏览 收藏
-
- 文章 · java教程 | 11小时前 | Java教程 · java MIDI设备 MidiSystem MidiDevice.Info javax.sound.midi
- Java MidiSystem 怎样枚举可用的 MIDI 设备
- 172浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 802次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 793次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 881次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 850次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 682次使用
-
- Go Java 算法之字符串解码示例详解
- 2023-01-07 479浏览
-
- Go Java算法之单词搜索示例详解
- 2022-12-30 337浏览
-
- Gojava算法之括号生成示例详解
- 2023-02-22 128浏览
-
- GoJava算法之累加数示例详解
- 2023-01-07 149浏览
-
- GoJava算法最大单词长度乘积示例详解
- 2023-01-12 202浏览

