☰
JApiDocs替代Swagger:Spring Boot接口文档生成实践
2026/10/18 15:48:30 网站建设 项目流程

1. 为什么我最终把项目里的 Swagger 换成了 JApiDocs

先交代一下背景:我在某公司的后端组负责几个 Spring Boot 微服务,之前一直用 Swagger(springfox 那套)生成 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> <groupId>io.github.yedaxia</groupId> <artifactId>japidocs</artifactId> <version>1.4.4</version> <scope>provided</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 Result<OrderVO> 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,那么文档侧边栏会自动形成"order""user"两个分组,每个分组下再按类名列出接口条目。前端同事说这个分组视图比 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和required,JApiDocs 会展示字段名但丢失"是否必传"信息。我现在的习惯是:在@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 能不能导出 OpenAPI(Swagger 格式)的 JSON。答案是:官方没有直接支持,但存在一个社区方案,把 JApiDocs 生成的模型通过自定义模板映射成 OpenAPI JSON。这个在下文自定义模板部分会展开。

5. 实战踩坑记录:三个最容易翻车的场景

5.1 泛型返回类型导致文档参数显示不全

我第一次接入 JApiDocs 后,生成文档时发现一个问题:某个Result<T>包装类里,T这个泛型参数对应的实际类型在文档中显示成了Object,而不是具体的OrderVO。

后来定位到原因:JApiDocs 在解析带泛型的方法签名时,如果泛型变量在继承链中没有被"具体化"到某个确定的类型,它就不知道如何展示。换句话说,public Result<T> data()这类泛型方法签名,必须让 JApiDocs 能在源码中找到T的赋值类型。

我的解法是:在接口方法的返回类型里,不要写裸泛型,而是显式写明具体的泛型参数,例如:

// 正确写法 public Result<OrderVO> getOrderDetail(@RequestParam Long orderId) // 有问题的写法 public Result<T> getOrderDetail(@RequestParam Long orderId)

这个方法改动虽小,但直接影响了文档的可用性。前端拿到Result<OrderVO>能清楚地看到返回 data 里面是订单号、金额、状态等字段;而Result<T>则只能看到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 服务让前端在没有后端环境时也能先行开发。

具体流程是:

  1. 后端修改好接口代码和 Javadoc 注释;
  2. 执行Docs.buildMarkdownDocs生成 Markdown 文件;
  3. 脚本读取参数表格,识别字段名、类型、是否必传;
  4. 根据字段注释中的限制(如"取值范围 1-999")生成 Mock 模板;
  5. 将 Mock 模板更新到 Mock 服务配置。

这个流程跑通后,前后端并行开发的效率提升非常明显。因为 JApiDocs 的文档本身就是接口的最新定义,Mock 数据不会出现"前端等后端改完才跑得通"的问题。

在实际操作中,我建议给实体类的字段注释写得尽量"机器可读",比如用"必传"、"选填"和"取值范围"这些关键词,脚本解析起来会更准确。如果注释写得随意,Mock 生成的字段可能不符合真实约束。

8. 一点个人体会

从 Swagger 换到 JApiDocs,我最大的感受不是"工具变了",而是"写文档的方式变了"。以前我写接口文档,是在写完代码后,再去对照接口写注解;写注解时心里想的是 Swagger 的规范,而不是接口本身。但 JApiDocs 强迫我回归到最原始、最自然的 Javadoc 注释上——写注释的时候,我关注的是这个接口的业务说明、参数约束、返回含义。文档最终变成代码的一部分,而不是附加在代码上的额外负担。

如果你所在的团队厌倦了 Swagger 注解的臃肿和启动时的沉重,或者你只是希望给自己的 Spring Boot 项目留一份简洁、可版本化、不依赖运行时的接口文档,JApiDocs 值得你用一小时做个小 Demo,再决定要不要全面推广。我个人从 1.4.x 版本用到现在,最核心的一条经验是:想让文档好用,先让注释好写。只要团队把注释规范立起来,JApiDocs 生成的文档质量能超过大多数商业化 API 文档工具。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询