从Java代码生成图表
1. 概述
虽然设计工具与开发环境并非总是兼容,但能够以编程方式生成示意图和图表却极具价值。
在本教程中,我们将探讨几种从 Java 代码生成 UML 图的方法。首先,我们从静态分析工具入手,这些工具会扫描源代码或字节码,自动生成类图和依赖图。接着,我们会了解一些有助于以某种方式暴露架构边界的工具和框架。最后,我们将探索一种基于代理的方法,利用大语言模型(LLM)将代码差异(diff)转换为图表和人类可读的总结。
为了便于理解并拓展整体认知,我们将使用这些不同的方法为同一个演示应用程序生成各种类型的图表。该应用基于垂直切片架构构建,模拟了一个类似 Baeldung 自身的博客平台领域。
2. 为什么要用 UML 图
拉取请求(Pull Request)正变得越来越大。AI 辅助编码工具使得在一次提交中触及多个包下的十几个类变得轻而易举:提取新接口、重新连线依赖关系、在不同层之间转移职责——所有这些都可以在几分钟而非几小时内生成。
因此,代码评审日益成为瓶颈。主要原因是评审者需要仅凭 diff 来重构变更的形态。然而,一个逐行可读的 diff 并不一定能让人轻松理解变更。在增删代码中滚动翻阅,很难看出哪些类现在依赖于哪些类。此外,组件经常可能悄无声息地跨越不应跨越的边界。
这正是图表所要解决的问题。一张好的图表可以在评审者阅读方法部分之前,就为其提供结构性的总结。图表能显示哪些组件被添加、哪些关系发生变化、以及变更的实际影响范围。通过从变更前后的状态——或者直接通过 diff——生成图表,可以将一堵文字墙转化为一眼就能理解的内容。
3. 静态代码分析
获取图表最确定性的方法就是直接从代码生成——无需手动建模、无需 LLM 介入,只需一个工具扫描类文件或源代码树,然后输出图表描述。这些工具通过解析导入语句、包结构和类关系来工作,并将结果渲染为 PlantUML 或 Mermaid 格式。
IntelliJ IDEA 内置了图表支持,可以可视化包、模块或所选 diff 涉及到的类。
让我们看看在经典 IDE 中直接生成 Java 类图的步骤:
- 右键点击项目的根目录或某个 Java 包。
- 选择 Diagrams > Show Diagrams。
- 选择 Java Classes 作为想要生成的图表类型。
这些操作都在一个子窗口中完成: [LOADING...]
IntelliJ 现在会在一个新标签页中渲染类图。默认情况下图表是可交互的而非导出,但可以通过工具栏的导出按钮将其保存为 .png、.svg 或 .puml 文件: [LOADING...]
或者,我们可以使用像 PlantUML Parser 这样的 IDE 插件,它允许我们右键点击任何 Java 文件或目录,选择 PlantUML Parser,并直接生成一个 .puml 文件: [LOADING...]
毋庸置疑,还有其他众多选择。仍在使用 Eclipse 的团队也有自己的选项,例如较旧的插件 AmaterasUML 也能在该 IDE 中解决同样的问题。
在 IDE 之外,Java2PlantUML 通过命令行采用类似的方法,扫描编译后的字节码或源代码并生成 .puml 文件,这使其非常适合 CI 流水线。
4. 框架级别的架构源
静态分析工具不了解意图——它们只能看到类和包,而非架构边界。然而,有些框架已经确切知道边界在哪里,因为我们已经显式声明了它们。
与其从字节码逆向工程结构,不如将这些框架级别的声明视为图表的真实来源。一些框架甚至更进一步,允许我们在构建过程中高亮并强制实施这些边界。
Spring Modulith 就是一个很好的例子。具体来说,Spring Modulith 允许我们将 Spring Boot 应用组织成显式模块,每个模块对应一个顶级包,并在构建时验证模块只能通过其公共 API 进行交互:
我们可以看到,除了验证模块结构之外,我们还可以直接从它生成文档。运行 createModuleDocumentation() 会在 target/spring-modulith-docs 中生成一组 PlantUML 图:
[LOADING...]
其他框架如 Spring Cloud Stream 则允许我们以声明式方式在单个集中式配置文件中定义数据在应用中的流动。由于这些绑定位于一处而非分散在各个注解中,我们可以轻松读取该文件,并用 Mermaid 或 PlantUML 生成序列图或流程图,展示数据如何在服务之间移动。
虽然这些框架帮助文档化单个服务内部发生的事情,但它们并未告诉我们该服务如何与外部世界通信——这正是契约发挥作用的地方。扫描契约文件是了解不同服务如何连接的好方法。这包括 OpenAPI 和 AsyncAPI 文档,或者如果我们实施了契约测试,还包括 Spring Cloud Contract 和 Pact 契约。
5. 带代理介入的生成方式
静态分析和框架级别的源都有局限性。本质上,它们只能描述已经以机器可读形式存在的结构。两者都无法告诉我们为什么做出了某个变更,也无法用通俗的语言为评审者总结变更。
这正是基于大语言模型(LLM)的代理发挥作用的地方。与其确定性解析代码,不如触发一个轻量级代理(如 Claude Haiku)。它直接读取 diff,并生成图表以及简短的、人类可读的变更总结。有两个自然的位置可以接入。
第一个是作为 CI 流水线中的一个步骤,例如 一个 GitHub Actions 或 GitLab CI 作业,在每个拉取请求时运行,与目标分支进行 diff,并将生成的图表和总结发布为 PR 评论:
另一方面,我们也可以使用一个预提交钩子(pre-commit hook),该钩子使用本地 AI 工具(如 Claude Code 或 Copilot CLI),这样文档就会与变更本身一起生成并提交。
让我们看一个简化版本的钩子,我们可以将其保存为 .githooks/pre-commit:
每次对项目进行提交时,该钩子会执行以下操作:
- 对暂存的更改进行 diff
- 请求 Claude Haiku 提供简短总结和 Mermaid 组件图
- 将生成的 Markdown 文件随提交一起暂存
让我们对代码库做一些本地更改并提交,看看预提交钩子的实际效果: [LOADING...]
正如预期,模型总结了更改并生成了一个关于最近提交所影响关键组件的简单图表,使评审者的工作更加轻松。
6. 结论
在本文中,我们探讨了几种从 Java 代码生成图表的方法,以便随着代码差异(diff)越来越大、仅凭原始代码难以理解时,减轻代码评审的难度。首先,我们从静态代码分析开始,使用 IntelliJ 内置图表支持、PlantUML Parser 插件以及 Java2PlantUML 等工具直接从源代码或字节码生成图表。
接着,我们考察了框架级别的架构源,其中像 Spring Modulith 和 Spring Cloud Stream 这类工具允许我们将已声明的边界和数据流视为图表的真实来源。然后,我们还讨论了像 OpenAPI、AsyncAPI 或 Pact 这样的契约如何能够记录服务之间的交互。
最后,我们探索了带代理介入的方式,通过 CI 流水线或预提交钩子触发一个轻量级 AI 代理,将 diff 转换为图表和人类可读的总结。
像往常一样,本文的代码可在 GitHub 上获取。
文章 从 Java 代码生成图表 最初发布在 Baeldung 上。