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