告别手写文档,让代码成为 API 的唯一真相
前后端分离开发中,接口文档的质量直接影响协作效率。文档更新滞后、格式混乱、示例缺失——这些问题几乎每个团队都遇到过。传统做法是用 Word 或 Markdown 手写文档,但代码改了文档没改的情况太常见了,前端开发调试到一半才发现接口已经变了。
解决这个问题的思路其实很直接:让文档从代码里自动生成。
SpringDoc 就是干这件事的。它扫描 Spring Boot 项目里的 Controller 和实体类,自动生成符合 OpenAPI 3 规范的文档,然后配上 Swagger UI 或者 Knife4j 这样的界面,前端可以直接在浏览器里查看接口、调试请求。
本文从零开始,用一个完整的 Spring Boot 3.5 项目演示 SpringDoc 的集成、配置、注解使用,以及如何通过 OpenAPI 规范打通前后端协作的完整链路。
一、先搞清楚这几个概念
很多人把 Swagger、OpenAPI、SpringDoc 混为一谈,聊起来总差那么点意思。
Swagger 最早是一套工具集,包含 Swagger UI(交互式文档界面)、Swagger Editor(API 设计编辑器)等。它提出的那个 API 描述规范,后来捐给了 Linux 基金会,改名叫 OpenAPI 规范——现在最新的版本是 3.1。
SpringDoc 是一个 Java 库,它的工作是扫描 Spring Boot 项目里的 @RestController、@GetMapping 这些注解,自动生成一份符合 OpenAPI 3 规范的 JSON 或 YAML 文件。然后 Swagger UI 或者 Knife4j 读取这份文件,渲染成可交互的文档页面。
简单来说:SpringDoc 负责生产数据,Swagger UI/Knife4j 负责展示数据。
为什么不用 SpringFox 了?
如果你之前用过 Swagger,大概率接触过 SpringFox——那个依赖 springfox-boot-starter 的老方案。
SpringFox 在 2020 年 7 月发布 3.0.0 版本后就基本停更了,GitHub 上的 issue 和 PR 越堆越多。到了 Spring Boot 3.x 时代,底层从 javax.* 换成了 jakarta.*,SpringFox 根本不兼容,启动直接报错。
SpringDoc 从一开始就是为 OpenAPI 3 和 Spring Boot 3.x 设计的,社区活跃,版本更新快,已经是事实上的标准替代方案。如果你的项目还在用 SpringFox,趁早换。
二、基础集成:三步跑起来
2.1 引入依赖
Spring Boot 3.5.x 对应 springdoc-openapi 的 2.x 版本(具体是 2.8.17),Spring Boot 4.x 才用 3.x 版本。
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.8.17</version>
</dependency>
这个依赖把 SpringDoc 核心库和 Swagger UI 的前端资源一起带进来了。如果不需要 UI 界面,只想导出纯 JSON/YAML 规范文件,可以用 springdoc-openapi-starter-webmvc-api。
2.2 零配置启动
依赖加完,启动项目,什么都不用配。SpringDoc 自动生效。
访问以下地址:
| 地址 | 说明 |
|---|---|
/v3/api-docs | OpenAPI 规范 JSON 格式 |
/v3/api-docs.yaml | OpenAPI 规范 YAML 格式 |
/swagger-ui.html | Swagger UI 交互界面 |
访问 /swagger-ui.html,能看到所有 Controller 自动生成了文档。点击某个接口,可以查看请求参数、响应结构,还能直接在页面上发起请求测试。
这就是 SpringDoc 最舒服的地方——加一个依赖,文档就有了,不需要写任何配置代码。
2.3 基础配置项
虽然零配置就能跑,但生产环境通常需要调整一些参数。在 application.yml 里可以这样配:
springdoc:
# API 文档的访问路径,默认 /v3/api-docs
api-docs:
path: /api-docs
# OpenAPI 规范版本,2.8.0 之后默认是 3.1
version: OPENAPI_3_1
# Swagger UI 的访问路径
swagger-ui:
path: /swagger-ui.html
# 是否展开所有接口,默认只展开 Tags
operations-sorter: method
tags-sorter: alpha
# 要扫描的包路径,多个用逗号分隔
packages-to-scan: com.example.demo.controller
# 是否显示 Actuator 端点(如果有)
show-actuator: false
springdoc.api-docs.version 这个参数需要注意:SpringDoc 从 2.8.0 开始默认使用 OpenAPI 3.1 规范。如果想保持 3.0,可以设为 OPENAPI_3_0。OpenAPI 3.1 和 3.0 的主要区别后面会讲。
三、用注解把文档写清楚
SpringDoc 自动生成的文档只能覆盖基本信息——接口路径、HTTP 方法、参数名。要想让文档真正可用,需要配合 Swagger 的注解来补充说明。
3.1 接口层面:@Operation 和 @Tag
@Tag(name = "用户管理", description = "用户注册、登录、信息查询相关接口")
@RestController
@RequestMapping("/api/users")
public class UserController {
@Operation(
summary = "根据 ID 查询用户",
description = "返回用户的完整信息,包含 profile 和最近订单列表"
)
@GetMapping("/{id}")
public User getUser(@Parameter(description = "用户 ID", example = "1001")
@PathVariable Long id) {
// ...
}
}
@Tag 给 Controller 归类,@Operation 描述单个接口的功能。前端开发和测试人员看文档时,不需要读代码就能知道接口是干什么的。
3.2 参数层面:@Parameter 和 @Schema
@PostMapping("/search")
public Page<User> searchUsers(
@Parameter(description = "用户名模糊匹配", example = "张")
@RequestParam(required = false) String name,
@Parameter(description = "用户状态:0-禁用 1-启用 2-待审核")
@RequestParam(required = false) Integer status,
@ParameterObject // 将多个查询参数封装成对象
Pageable pageable
) {
// ...
}
@ParameterObject 是 SpringDoc 2.8.0 开始增强的功能,可以把分页、排序这类多个参数自动展开到文档里。
3.3 实体层面:@Schema
实体类的字段说明是最容易被忽略的。没有 @Schema,文档里只能看到字段名和类型,前端根本不知道每个字段的含义和取值范围。
@Data
@Schema(description = "用户信息")
public class User {
@Schema(description = "用户唯一标识", example = "1001")
private Long id;
@Schema(description = "用户姓名", required = true, example = "张三")
@NotBlank
private String name;
@Schema(description = "邮箱地址", example = "zhangsan@example.com")
@Email
private String email;
@Schema(description = "用户状态", allowableValues = {"0", "1", "2"},
example = "1")
private Integer status;
@Schema(description = "创建时间", example = "2026-08-01T10:30:00")
private LocalDateTime createdAt;
}
SpringDoc 2.8.0 开始能自动识别 @NotNull、@NotBlank 这类校验注解,把对应字段标记为必填。但为了文档的可读性,还是建议手动写清楚 description 和 example。
四、分组文档:让不同的人看不同的接口
微服务项目里,接口可能分成好几类——对外 API、内部 API、管理后台 API。把所有接口堆在一个文档里,对使用者来说很不友好。
SpringDoc 的 GroupedOpenApi 可以按路径或包来分组:
@Configuration
public class OpenApiConfig {
/**
* 对外 API 分组:/api/v1/** 路径下的接口
*/
@Bean
public GroupedOpenApi externalApi() {
return GroupedOpenApi.builder()
.group("external")
.displayName("对外 API")
.pathsToMatch("/api/v1/**")
.build();
}
/**
* 管理后台 API 分组:/api/admin/** 路径下的接口
*/
@Bean
public GroupedOpenApi adminApi() {
return GroupedOpenApi.builder()
.group("admin")
.displayName("管理后台 API")
.pathsToMatch("/api/admin/**")
.addOpenApiCustomizer(openApi ->
openApi.info(new Info().title("管理后台 API").version("1.0")))
.build();
}
/**
* 内部服务 API 分组:按包名扫描
*/
@Bean
public GroupedOpenApi internalApi() {
return GroupedOpenApi.builder()
.group("internal")
.displayName("内部服务 API")
.packagesToScan("com.example.demo.internal")
.build();
}
}
配置好之后,Swagger UI 左上角会出现下拉菜单,可以在不同分组之间切换。前端只看 external 分组,运维看 admin 分组,各取所需。
五、Swagger UI 的交互调试
SpringDoc 自带的 Swagger UI 不光是“看”文档的,还能直接“调”接口。
打开 /swagger-ui.html,每个接口右边都有一个 “Try it out” 按钮。点一下,输入参数,点击 “Execute”,Swagger UI 会向你的后端发起真实请求,并展示响应结果。
这个功能在开发阶段非常实用——前端还没写好页面的时候,后端自己就能把接口调通;测试人员也可以直接用 Swagger UI 做冒烟测试,不用单独写测试脚本。
有几个小配置可以让 Swagger UI 更好用:
springdoc:
swagger-ui:
# 按 HTTP 方法排序(GET → POST → PUT → DELETE)
operations-sorter: method
# Tag 按字母排序
tags-sorter: alpha
# 默认展开所有接口
doc-expansion: none # none | list | full
# 请求超时时间(毫秒)
try-it-out-enabled: true
# 持久化请求参数(刷新页面不丢失)
persist-authorization: true
六、OpenAPI 3.1:升级之后有什么不一样?
SpringDoc 2.8.0 开始默认使用 OpenAPI 3.1 规范。和 3.0 相比,有几个值得注意的变化:
JSON Schema 2020-12 对齐。3.1 完全兼容 JSON Schema 的最新草案,支持 allOf、anyOf、oneOf 这些组合模式。
nullable 语义变了。3.0 里用 nullable: true 表示字段可以为 null,3.1 里改成了类型数组的方式:type: ["string", "null"]。如果你从 3.0 升级上来,要注意这个变化。
Webhook 原生支持。OpenAPI 3.1 第一次把 Webhook 作为一等公民。SpringDoc 从 2.8.0 开始支持 @Webhook 注解,但目前只支持类级别,方法级别的支持还在完善中。
安全方案定义更灵活。3.1 增强了安全方案的定义能力,支持更细粒度的 OAuth2 流程描述。
对于大多数项目来说,从 3.0 升级到 3.1 不需要改代码,SpringDoc 会自动处理规范的差异。唯一需要注意的是,如果前端使用了 OpenAPI Generator 等工具生成客户端代码,要确认工具是否支持 3.1——目前部分工具对 3.1 的支持还不完整。
七、Knife4j:让文档更好看
Swagger UI 功能够用,但界面风格偏朴素。国内开发者用得比较多的替代方案是 Knife4j。
Knife4j 不改变 SpringDoc 的后端逻辑,只是替换了前端展示层。它把 SpringDoc 生成的 OpenAPI JSON 拿过来,渲染成功能更丰富、界面更现代化的文档页面。
引入 Knife4j 非常简单——直接替换依赖就行:
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
<version>4.5.0</version>
</dependency>
这个依赖内部已经包含了 SpringDoc,不需要再单独引入。
启动项目后,访问 /doc.html 就能看到 Knife4j 的界面。它比 Swagger UI 多了几个实用的功能:
- 接口搜索:接口多了之后直接搜索比翻菜单快得多
- 离线文档导出:支持导出 Markdown、HTML、Word 格式,方便归档和交付
- 全局参数:可以统一设置认证 Token,不用每个接口单独填
- 深色模式:这个纯粹是看着舒服
选 Swagger UI 还是 Knife4j,看团队偏好。功能上都能满足日常开发,Knife4j 的界面更精致一些。
八、打通前后端协作:从 OpenAPI 到 SDK
文档的最终目的不是“给人看”,而是“让人用”。OpenAPI 规范最大的价值在于——它可以作为机器可读的接口契约,直接生成各语言的客户端 SDK。
8.1 导出 OpenAPI 规范文件
先拿到 OpenAPI 的 JSON 或 YAML 文件:
# 导出 JSON 格式
curl http://localhost:8080/v3/api-docs > openapi.json
# 导出 YAML 格式(更易读)
curl http://localhost:8080/v3/api-docs.yaml > openapi.yaml
8.2 生成 TypeScript 客户端
前端拿到 openapi.yaml,可以用 OpenAPI Generator 生成 TypeScript 客户端代码:
npx @openapitools/openapi-generator-cli generate \
-i openapi.yaml \
-g typescript-axios \
-o ./src/api
生成的代码里包含了所有接口的请求函数和类型定义。前端直接 import 进来用,不需要手写任何 API 调用代码。
8.3 生成 Java 客户端
后端服务之间相互调用时,也可以用同样的方式生成 Java 客户端:
openapi-generator-cli generate \
-i openapi.yaml \
-g java \
-o ./java-client
8.4 规范先行 vs 代码先行
这里涉及到两种 API 开发模式:
- 代码先行(Code-First):先写 Controller 代码,用 SpringDoc 生成 OpenAPI 规范。适合快速迭代、接口变动频繁的项目。
- 规范先行(Design-First):先设计 OpenAPI 规范文件,再用工具生成 Controller 接口和实体类。适合接口稳定、需要多方评审的项目。
SpringDoc 走的是代码先行路线。对于大多数敏捷团队来说,代码先行更符合实际工作节奏——接口改了,文档自动跟着变,不会出现“代码和文档不一致”的问题。
九、生产环境配置
Swagger UI 在开发阶段很有用,但暴露到外网就有安全风险了——攻击者可以通过它了解你的所有接口结构。
生产环境建议做两件事:
第一,关闭 Swagger UI:
springdoc:
swagger-ui:
enabled: false
api-docs:
enabled: false
第二,用 profile 区分环境:
# application-dev.yml
springdoc:
swagger-ui:
enabled: true
api-docs:
enabled: true
# application-prod.yml
springdoc:
swagger-ui:
enabled: false
api-docs:
enabled: false
启动时指定 spring.profiles.active=prod,生产环境就自动关闭文档暴露。
十、总结
API 文档这件事,说大不大,说小不小。它不直接产生业务价值,但没有它,前后端协作的效率会大打折扣。
SpringDoc 做的事情其实很简单——把代码里的信息提取出来,变成一份结构化的接口描述。然后 Swagger UI 或 Knife4j 把它渲染成可读的文档,OpenAPI Generator 把它变成可用的 SDK。
从 SpringFox 到 SpringDoc,不仅仅是换了一个依赖那么简单。它代表了一种理念:代码是 API 的唯一真相来源,文档只是代码的投影。只要 Controller 和实体类保持了最新的状态,文档就不会过期。
如果你的项目还在用 Word 或 Markdown 维护接口文档,试试 SpringDoc。加一个依赖,启动项目,文档就自动生成了。剩下的时间,可以用来做更有价值的事情。
系列拓展阅读
- 《Spring Security 6 + JWT + OAuth2 实战:从零构建安全认证中心》 —— 在 Swagger UI 中集成 JWT 认证
- 《Spring Boot 微服务间调用最佳实践:从 OpenFeign 到 HttpExchange + 服务治理》 —— OpenAPI 生成 Feign 客户端的实践
- 《JUnit 5 + Testcontainers:Java 微服务集成测试最佳实践》 —— 基于 OpenAPI 规范做契约测试
- 《Java 重构实战:6 个代码坏味道的重构案例》 —— Controller 层代码重构与文档同步
参考文献
- SpringDoc OpenAPI Official Documentation. https://springdoc.org/
- SpringDoc OpenAPI v2.8.17 Release Notes. GitHub.
- OpenAPI Specification v3.1.0. https://spec.openapis.org/oas/v3.1.0
- “Swagger3 与 SpringDoc 的终极对比:为什么 Spring Boot 3.5.x 开发者应该选择后者.” CSDN, 2026.
- “SpringBoot 3.5 集成 Knife4j 4.3 步骤.” CSDN, 2026.
- “SpringDoc OpenAPI 2.8.0版本发布:全面拥抱OpenAPI 3.1标准.” 博客园, 2025.
- “Knife4j与Springdoc的完美搭配:SpringBoot3下的API文档增强实践.” CSDN, 2026.
- OpenAPI Generator Documentation. https://openapi-generator.tech/









