资讯动态

JApiDocs替代Swagger:Spring Boot接口文档生成实践

发布时间:2026/10/18 15:48:34 来源:尧图企业网站定制
1. 为什么我最终把项目里的 Swagger 换成了 JApiDocs先交代一下背景我在某公司的后端组负责几个 Spring Boot 微服务之前一直用 Swaggerspringfox 那套生成 API 文档。刚开始觉得挺好控制器方法一写注解一标文档就能自动出来前端同事也不用老来问参数格式。但用了一年后问题越来越明显——启动速度越来越慢一个中等规模的服务启动要 30 多秒其中一大半时间花在 Swagger 的文档扫描和模式解析上内存占用也高本地开发经常因为 PermGen 不够报 OutOfMemory更麻烦的是注解写起来啰嗦ApiImplicitParams、ApiParam、ApiModelProperty堆满整个控制器代码整洁度直线下降。后来有一次和群里几个同行聊起这个话题有人提到了 JApiDocs。我一开始没当回事毕竟 Swagger 已经形成生态习惯了。直到某次项目紧急需要给前端出一份离线接口文档Swagger 的 JSON 导出来一堆乱码用 swagger2markup 转换后又丑又难维护我才认真研究了一下 JApiDocs 这个工具。JApiDocs 的核心思路是注释即文档——不需要写任何额外注解直接通过解析 Java 源码里的 Javadoc 注释来生成文档。它把文档这件事的粒度从注解驱动彻底换成了源码原生驱动。对于 Spring Boot 项目来说这意味着不需要引入一堆运行时依赖也就不会拖慢启动速度文档紧跟代码注释改了生成的文档就变了天然避免改代码忘改文档的问题生成的是静态站点/静态文件可以直接放在服务器上给前端、测试、产品看也可以纳入 Git 版本管理。我用了一个下午把项目里某个订单服务模块迁移过去测了几个接口效果远超预期。这篇文章就把我从入门到实际用坑的全部经验整理出来希望能帮到所有还在被 Swagger 注解折磨的 Spring Boot 开发者。2. 环境准备与 Spring Boot 项目接入的全流程2.1 导入依赖与版本选择的细节JApiDocs 目前最新稳定版是 1.4.4截至我写这篇文章时的经验官方发布的 Maven 坐标为io.github.yedaxia:japidocs。在你的 Spring Boot 项目pom.xml中添加dependency groupIdio.github.yedaxia/groupId artifactIdjapidocs/artifactId version1.4.4/version scopeprovided/scope /dependency这里有个容易被忽略的关键点scope建议用provided。因为这个工具只在编译期做源码解析和文档生成运行时完全不需要它用provided可以避免把无关的依赖打到生产包中。我第一次接入时图省事没加 scope结果打出来的 fat jar 胖了好几 MB排查半天才发现是这个原因。JDK 环境方面JApiDocs 是基于 Java 源码解析器JavaParser实现的对 JDK 版本兼容性做得比较激进。我在 JDK 8、JDK 11、JDK 17 三个环境下都跑过都没问题。如果你用的是 JDK 21 或更新版本建议先跑一个小 Demo 验证一下我目前还没有在 JDK 21 上实际测试过不确定新版javadoc注释格式是否有兼容性问题。2.2 最基础的生成入口写一个 main 方法即可这里和 Swagger 最大的区别就体现出来了。Swagger 是运行时通过拦截器扫描RestController下的所有 RequestMapping 元数据而 JApiDocs 是一个普通的 Java 库你只需要在任意地方写一个main方法就能生成文档。最基础的用法是import io.github.yedaxia.apidocs.Docs; import io.github.yedaxia.apidocs.DocsConfig; public class ApiDocGenerator { public static void main(String[] args) { DocsConfig config new DocsConfig(); config.setProjectPath(你的项目根路径); // 绝对路径要求是包含 pom.xml 的根目录 config.setProjectName(订单服务 API); config.setApiVersion(V1.0.0); config.setDocsPath(你的项目根路径/src/main/java); config.setAutoGenerate(Boolean.TRUE); config.setMvcPattern(com.example.controller.*); // 控制器包名通配符 Docs.buildHtmlDocs(config); } }把这个 main 方法放在src/test/java目录下的某个工具类中即可既不影响运行时代码又能方便地随时执行。执行完成后你会发现在项目根目录下多了一个docs文件夹里面包含了index.html等一批静态文件。直接用浏览器打开index.html一份完整的、带侧边栏导航的 API 文档就展现在眼前了。整个过程干净利落没有容器启动没有端口占用没有任何运行时依赖。2.3 配置项逐个拆解项目里实际用到的几个参数JApiDocs 的配置核心是DocsConfig类下面几个字段是按重要性排序的我逐个解释用途和选值的依据配置项作用我的建议值setProjectPath项目根路径用于解析源码文件必须是包含pom.xml的绝对路径多模块项目定位到聚合根目录setDocsPath要扫描的 Java 源码目录单模块用src/main/java多模块需按模块分别扫描setMvcPattern控制器类的包名通配符过滤要生成文档的类只写 Controller 所在包避免扫描到无关类加快生成速度setAutoGenerate是否自动生成 Model 类文档为true时会自动为文档里出现的实体类生成字段说明setProjectName文档显示的项目名称建议写业务系统名而不是代码模块名前端同事看起来更友好另外还有一个setTrustHost之类的测试相关配置那个是给官方调试用的实际项目基本用不到不用理会。配置文件路径这块我踩过一次坑如果你用 Maven 多模块项目每个模块一个src/main/java那么setDocsPath只能填一个目录。解决方法是分多次生成或者使用多模块聚合方案后面我会单独讲。3. 注释怎么写JApiDocs 才能生成高质量文档3.1 控制器类注释的规范和示例JApiDocs 识别文档信息的来源完全依赖 Javadoc 注释所以注释写得好不好直接决定了文档质量。先说控制器类级别的注释规范。来看一个标准的示例/** * 订单接口 * * 这里可以写一段更详细的描述例如 * 本模块负责订单的创建、查询、取消等操作供商城前端和后台管理系统调用。 * 所有接口在调用前均需通过网关鉴权请求头中需携带 token。 * * author 某开发者 * date 2024-06-01 */ RestController RequestMapping(/api/order) public class OrderController { }类注释中的date、author等内容 JApiDocs 会原样显示到文档页面的描述区域这些信息对于团队内部明确接口负责人很有用。但要注意的是JApiDocs 不会解析author里的邮箱、电话等隐私格式它只会做纯文本展示。所以不要在注释里放敏感信息。3.2 方法注释里的 param 和 return 是重中之重单个接口方法的文档质量完全取决于param和return有没有写清楚。JApiDocs 会把 Javadoc 中每个param对应到接口的一个请求参数所以在方法注释里写参数说明就是定义接口文档参数的最直接方式。看一个典型的 POST 接口写法/** * 创建订单 * * 新订单创建后状态为待支付超时未支付会被自动取消。 * * param req 创建订单请求体包含商品明细、收货地址等信息 * return 创建成功的订单信息包含订单号和订单金额 */ PostMapping(/create) public ResultOrderVO createOrder(RequestBody OrderCreateReq req) { return orderService.create(req); }这里要特别注意JApiDocs 会自动根据方法签名推断请求类型POST、GET、路径/api/order/create和参数位置body 参数还是 query 参数。如果你想让req对象展开为内层的字段即多个子参数只要在OrderCreateReq类的字段上加注释即可这个下面接着说。3.3 实体类字段注释直接对应文档的 Model 表格这也是 JApiDocs 最让我舒心的一点实体类不需要任何注解字段的参数名天然是 Java 字段名字段的含义天然取自字段注释。public class OrderCreateReq { /** * 商品编号必传 */ private String productId; /** * 购买数量取值范围 1-999 */ private Integer quantity; /** * 收货地址ID需提前在用户地址簿中创建 */ private Long addressId; /** * 订单备注选填最长 100 字 */ private String remark; // 省略 getter/setter }这样写完后生成的 HTML 文档中关于请求参数的表格会自动列出productId、quantity、addressId、remark以及对应的中文说明。整个流程没有任何注解参与Java 代码就是文档的唯一来源。如果你在字段注释中用see标签指向另一个类JApiDocs 还会自动生成关联跳转这个可以用在一些嵌套对象的场景上。3.4 让文档更直观的进阶注释技巧我在实际项目里慢慢摸索出几个让文档更好用的小技巧在方法注释里用p标签分隔多个段落JApiDocs 会保留 HTML 换行文档更清晰在字段注释里写清楚传参格式例如格式: yyyy-MM-dd HH:mm:ss前端直接照着拼就行了在类注释里列出该接口模块的注意事项例如所有金额单位为分这类容易引起联调歧义的信息在deprecated注释里写清楚推荐替代哪个接口JApiDocs 会在文档中标注过时标记避免前端误用。注意JApiDocs 对 Javadoc 的解析是区块式的它不太支持把多个标签挤在同一行。所以每个param尽量独占一行保持规范解析才稳定。4. 实际使用中的三组核心功能分组、参数映射和离线文档4.1 多控制器分组让接口目录不再大锅汇一个中大型 Spring Boot 服务的 Controller 少则五六个多则十几个。JApiDocs 默认会把扫描到的所有 Controller 全部平铺在文档左侧栏看起来就像菜市场一样混乱。实际上JApiDocs 提供了基于包名的分组逻辑。只需要把不同业务模块的 Controller 分别放在不同的包下然后在mvcPattern中制定匹配规则JApiDocs 就会按照包结构生成层级目录。举例来说config.setMvcPattern(com.example.order.*.controller.*);如果你把订单相关控制器放在com.example.order.controller把用户相关控制器放在com.example.user.controller那么文档侧边栏会自动形成orderuser两个分组每个分组下再按类名列出接口条目。前端同事说这个分组视图比 Swagger 按注解分组好用多了——因为注解分组是逻辑上的代码路径分组是物理上的按代码结构来找接口会更直观。对于跨多个系统的大型项目也可以在启动参数里通过多个mvcPattern配合多个 Controller 的包路径实现更细粒度分组。我实际的做法是每个后端模块独立生成一套文档然后通过下文要讲的多模块聚合把文档合并每个模块天然一个分组。4.2 RequestBody 和 PathVariable 的自动识别逻辑JApiDocs 的智能之处在于它不需要你额外指出某个参数是 body 还是路径参数它会自动根据 Spring MVC 的注解来判定。它的识别规则大致如下Spring 注解JApiDocs 识别结果RequestBody请求体body会在文档中展开为 JSON 对象形式PathVariable路径变量显示为路径替换占位符/api/user/{id}RequestParamquery 参数显示为表单键值对无注解且为普通类型默认视为 query 参数无注解且为自定义对象默认展开 body 参数我最初以为这样一个无注解的库在参数识别上会很弱实际测试后发现它对 Spring MVC 的标准注解识别准确率非常高。不过有一类特殊情况要多留意如果你在参数上同时加了RequestParam却没有指定value和requiredJApiDocs 会展示字段名但丢失是否必传信息。我现在的习惯是在RequestParam注解中显式写明required true/false同时确保字段注释里写清必传/选填双保险。4.3 生成 Markdown 和 PDF离线交付的几种方案Spring Boot 接口文档最常见的消费场景不仅仅是网页在线查看更多时候需要以 Markdown 或 PDF 形式交付给测试组、产品经理或外部合作方。JApiDocs 提供了多种输出格式// 生成 HTML Docs.buildHtmlDocs(config); // 生成 Markdown Docs.buildMarkdownDocs(config); // 生成 PDF底层走 HTML 导出后调用浏览器打印 Docs.buildPdfDocs(config);buildPdfDocs其实是在 HTML 基础上调用了内置的打印引擎所以对输出目录有一定要求。如果你只需要给外部交付一份不可编辑的文档Markdown 转 PDF 是更可控的路线。我个人更常用的组合是先buildHtmlDocs生成网页版再对 HTML 页面使用浏览器的另存为 PDF功能版式保持得最好。还有朋友问 JApiDocs 能不能导出 OpenAPISwagger 格式的 JSON。答案是官方没有直接支持但存在一个社区方案把 JApiDocs 生成的模型通过自定义模板映射成 OpenAPI JSON。这个在下文自定义模板部分会展开。5. 实战踩坑记录三个最容易翻车的场景5.1 泛型返回类型导致文档参数显示不全我第一次接入 JApiDocs 后生成文档时发现一个问题某个ResultT包装类里T这个泛型参数对应的实际类型在文档中显示成了Object而不是具体的OrderVO。后来定位到原因JApiDocs 在解析带泛型的方法签名时如果泛型变量在继承链中没有被具体化到某个确定的类型它就不知道如何展示。换句话说public ResultT data()这类泛型方法签名必须让 JApiDocs 能在源码中找到T的赋值类型。我的解法是在接口方法的返回类型里不要写裸泛型而是显式写明具体的泛型参数例如// 正确写法 public ResultOrderVO getOrderDetail(RequestParam Long orderId) // 有问题的写法 public ResultT getOrderDetail(RequestParam Long orderId)这个方法改动虽小但直接影响了文档的可用性。前端拿到ResultOrderVO能清楚地看到返回 data 里面是订单号、金额、状态等字段而ResultT则只能看到data: {}。5.2 多模块 Maven 项目如何聚合生成一份完整文档微服务项目一般按功能拆分成多个 Maven 模块api-gateway、user-service、order-service、common。每个模块都有独立的src/main/java那么 JApiDocs 的setDocsPath就很尴尬——它只能填一个目录。我尝试过几种方案最终可行的是写一个生成器 main 方法按模块多次创建DocsConfig并执行String root System.getProperty(user.dir); String[] modules {user-service, order-service, product-service}; for (String module : modules) { DocsConfig config new DocsConfig(); config.setProjectPath(root / module); config.setDocsPath(root / module /src/main/java); config.setMvcPattern(com.example. module.replace(-, ) .controller.*); config.setAutoGenerate(Boolean.TRUE); Docs.buildHtmlDocs(config); }注意这样生成的文档会各自写到对应模块的docs目录下。如果你希望所有服务接口一键聚合到一个统一的站点最简单的办法是在生成器 main 方法的设计上把所有模块的输出路径统一设置为同一个目录下的不同子目录即聚合根目录/docs/user-service/聚合根目录/docs/order-service/然后再用 Nginx 或静态网页工具如 docsify、VuePress做一个总入口按接口归属模块索引。我目前在公司内部就是这样做的一个总的API 文档站静态目录每次重新生成后由 CI 任务自动同步到内网服务器前端只用访问一个地址就能在侧边栏切换不同模块的接口文档。5.3 与 Spring Security/拦截器的冲突其实根本没有冲突很多刚接触 JApiDocs 的读者会担心我的项目里有 Spring Security接口必须登录才能调用那 JApiDocs 是不是识别不了这里要澄清一个概念JApiDocs 是编译期/独立运行期的源码解析工具它的main方法一旦执行就只做读 Java 源码 生成静态 HTML两件事。它完全不经过 Spring 容器既不启动应用也不拦截请求所以 Spring Security、拦截器、过滤器、AOP 这些运行时组件对它毫无影响。JApiDocs 不需要应用启动反而意味着你可以在开发环境没有数据库、没有 Redis、没有注册中心的情况下随时生成最新接口文档。这个特性在接口频繁变动的迭代期非常实用。而我在这部分踩到过唯一的假坑是有些接口方法上加了自定义注解比如RepeatSubmit、Idempotent这些注解不在 JApiDocs 的识别范围里它不会在文档里展示。如果你希望这些业务约束能体现在文档中最好的办法是在方法 Javadoc 中用p标注例如该接口支持幂等。6. 从入门到进阶自定义模板与集成 CI 的完整思考6.1 修改文档模板让文档打出公司品牌JApiDocs 的默认模板其实比较朴素类似开源项目的默认 API 文档风格。对于要交付给客户或者公司内部统一规范化使用的场景自定义模板就很有必要。JApiDocs 提供了模板扩展机制生成 HTML 文档时默认加载的模板位于 jar 包内的japidocs/resources/templates/下。你可以调整DocsConfig中setTemplatePath指向自定义模板目录config.setTemplatePath(classpath:custom-templates/);具体模板目录内需要覆盖的文件可以参照官方文档。我只补充两点实践心得模板是用 FreeMarker 编写的如果你的项目里没有用过 FreeMarker改模板最好先在本地跑通默认模板再逐步替换其中的变量自定义模板时注意 HTML 页面里引用的静态资源CSS、JS路径JApiDocs 会复制一份静态资源到输出目录如果你改模板却没有同步静态资源路径生成的页面样式可能会乱掉。我在一次给某政务类项目定制文档时只是把文档首页的 Logo、标题、页脚全部换成统一风格然后加上了公司统一的 favicon整体工作量大约一个上午比改造 Swagger UI 的样式要省事得多。6.2 与 Git 版本管理配合让每次接口变更有迹可循JApiDocs 生成的 HTML 是静态文件这给了它一个很大的优势可以纳入 Git 版本管理。每次接口变更后重新生成文档然后提交 Git相当于文档也有了版本历史。前端同事和测试同事可以通过 Diff 看到本次迭代的接口变更极大减少了沟通成本。在我目前的团队我把文档生成器写成一个 Test 类ApiDocGenerator然后在 CI 流水线的测试阶段末尾增加一个步骤执行这个 main 方法将生成的docs/目录上传到内网文档服务器。这样做的好处是不需要单独部署一套文档服务每个人本地跑一遍命令也能生成用于自测文档更新和历史记录天然沉淀在 Git 仓库里。6.3 多模块聚合方案的另一种思路先合并源码再生成前面提到多模块聚合可以通过多次调用来解决这里再提供一种我实测可行的思路在 CI 上把多个模块的src/main/java通过 symlink 或拷贝方式合并到一个临时目录然后只调用一次DocsConfig把docsPath指向临时目录mvcPattern使用覆盖所有模块的正则生成一份全量文档。但这个方案有一个局限如果两个模块有同名类比如各有一个Result包装类合并时会互相覆盖导致解析结果错乱。所以这个方案更适合包名隔离得足够好的项目。如果你对多模块结构没有那么强的一致性约束我还是推荐多次调用派生的方案稳定且直观。6.4 JApiDocs 与 Swagger 生态的边界什么时候不该用这篇文章虽然全篇都在讲 JApiDocs 的好但我还是要客观地说说它的边界避免读者盲目替换把自己坑了。以下场景我觉得还是保留 Swagger 或 SpringDoc 更合适团队对 OpenAPI 规范有硬性要求例如接口文档需要直接导入 Postman、Apifox 或某些 API 网关产品。JApiDocs 目前没有官方的 OpenAPI 导出能力虽然社区有 hack 方案但维护成本高需要在线调试接口。JApiDocs 生成的文档是纯静态的不能像 Swagger UI 那样直接在页面上发起请求调接口。对于前端联调阶段来说这个缺失还是有点明显。不过我们团队已经改用 Apifox 来直接联调JApiDocs 负责提供数据结构参考两者互补效果很好对象关系复杂、接口数量庞大且依赖注解驱动的特殊逻辑。JApiDocs 的解析规则相对简单直接遇到非常复杂的泛型嵌套时虽然不至于失败但展示效果可能不尽如人意。简而言之JApiDocs 更适合文档为主、联调为辅的团队协作模式如果团队已经重度依赖 Swagger 的调试功能和 OpenAPI 联结生态迁移要慎重。7. 生产环境里的一个优化实践从文档到 Mock 数据最后分享一个我认为很有价值的进阶用法。JApiDocs 生成的 Markdown 文档中每个接口的参数表和返回值结构都是结构化的。我写了一个小脚本解析 JApiDocs 生成的 Markdown 表格自动生成对应接口的 Mock 数据随机生成符合字段说明的示例参数然后配合 Mock 服务让前端在没有后端环境时也能先行开发。具体流程是后端修改好接口代码和 Javadoc 注释执行Docs.buildMarkdownDocs生成 Markdown 文件脚本读取参数表格识别字段名、类型、是否必传根据字段注释中的限制如取值范围 1-999生成 Mock 模板将 Mock 模板更新到 Mock 服务配置。这个流程跑通后前后端并行开发的效率提升非常明显。因为 JApiDocs 的文档本身就是接口的最新定义Mock 数据不会出现前端等后端改完才跑得通的问题。在实际操作中我建议给实体类的字段注释写得尽量机器可读比如用必传、选填和取值范围这些关键词脚本解析起来会更准确。如果注释写得随意Mock 生成的字段可能不符合真实约束。8. 一点个人体会从 Swagger 换到 JApiDocs我最大的感受不是工具变了而是写文档的方式变了。以前我写接口文档是在写完代码后再去对照接口写注解写注解时心里想的是 Swagger 的规范而不是接口本身。但 JApiDocs 强迫我回归到最原始、最自然的 Javadoc 注释上——写注释的时候我关注的是这个接口的业务说明、参数约束、返回含义。文档最终变成代码的一部分而不是附加在代码上的额外负担。如果你所在的团队厌倦了 Swagger 注解的臃肿和启动时的沉重或者你只是希望给自己的 Spring Boot 项目留一份简洁、可版本化、不依赖运行时的接口文档JApiDocs 值得你用一小时做个小 Demo再决定要不要全面推广。我个人从 1.4.x 版本用到现在最核心的一条经验是想让文档好用先让注释好写。只要团队把注释规范立起来JApiDocs 生成的文档质量能超过大多数商业化 API 文档工具。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑