Note 12. SpringBoot3 API 文档工程化:SpringDoc 与 OpenAPI 3
Note 12. SpringBoot3 API 文档工程化:SpringDoc 与 OpenAPI 3
Prorise第十二章. API 文档工程化:SpringDoc 与 OpenAPI 3 完全指南
摘要: 手写 API 文档不仅枯燥乏味,而且永远追不上代码的变更速度,是团队协作效率的“头号杀手”。本章,我们将彻底告别这个“石器时代”的工作模式,拥抱 Spring Boot 3 时代的唯一官方推荐方案——SpringDoc。我们将从一个全新的 Spring Boot 项目开始,亲手搭建一个用户管理模块,然后一步步集成基于 OpenAPI 3 规范的新一代文档工具。我们不仅能实现 API 文档的自动生成与实时同步,还将学会如何通过注解精细化地描述接口、模型和参数。更重要的是,我们将攻克企业级项目中至关重要的 JWT 认证 配置,最终生成一份美观、可交互、支持在线调试的专业级 API 文档。
本章学习路径
- 项目初始化与“问题”复现:我们将从零开始,使用
start.spring.io创建一个标准的 Spring Boot 3 项目,并编写基础的用户管理接口,直观感受“无文档”带来的协作困境。 - 技术演进与选型:我们将深入探讨为什么在 Spring Boot 3 时代,基于 OpenAPI 3 的 SpringDoc 是取代传统 SpringFox (Swagger 2) 的必然选择。
- 零配置快速集成:我们将体验 SpringDoc 带来的“开箱即用”的便利,仅需一步引入依赖,即可拥有一个功能完备但略显粗糙的 Swagger UI 界面。
- 文档精细化注解:我们将系统性地学习
@Tag,@Operation,@Parameter,@Schema等核心注解,像“精装修”一样,将原始文档变得信息丰富、清晰易读。 - 攻克 JWT 全局认证:我们将通过代码配置,为 Swagger UI 添加全局的
Authorization输入框,彻底解决需要认证的接口无法在线调试的痛点。 - 架构美学与体验升级:我们将学习如何通过
GroupedOpenApiBean 按业务模块进行接口分组,并引入 Knife4j 增强 UI 界面,提升文档的专业性和易用性。 - API 版本管理实战:我们将探讨在项目迭代中如何优雅地管理 V1 和 V2 版本的接口,并让文档清晰地呈现不同版本。
- 打通团队协作生态:我们将学习如何利用生成的 OpenAPI 规范,与 Postman、Apifox 等工具联动,实现自动化测试和前端 Mock 开发。
12.1. 一切的开始:从一个无文档的项目说起
在探讨任何解决方案之前,我们必须先切身体会问题所在。让我们遵循企业级项目开发的标准流程,从零开始搭建一个简单的用户管理服务,并观察“无文档”这只“拦路虎”是如何出现的。
12.1.1. 步骤一:创建 Spring Boot 3 项目骨架
我们访问 Spring 官方的脚手架工具 https://start.spring.io。
按照下列表进行配置,构建一个现代化的 Spring Boot 项目基础:
- Project: Maven
- Language: Java
- Spring Boot: 3.2.x 或更高稳定版
- Project Metadata:
- Group:
com.example - Artifact:
springdoc-demo - Name:
springdoc-demo - Packaging: Jar
- Java: 17
- Group:
- Dependencies:
Spring Web: 构建 Web 应用,包括 RESTful 服务。Lombok: 通过注解简化 JavaBean 的开发,如@Data、@Getter等。
点击 “GENERATE”,下载项目压缩包并用 IntelliJ IDEA 打开。Maven 会自动下载所需的依赖。
12.1.2. 步骤二:编写“毛坯房”式的 API 接口
我们的目标是创建一套基础的用户增删改查接口。为此,我们需要创建对应的 Controller、VO (View Object) 和 DTO (Data Transfer Object)。
1. 创建数据传输对象 (DTO/VO)
首先,我们需要定义用于前端数据交互的对象。在 com.example.springdocdemo 包下创建 dto 和 vo 两个新包。
文件路径: src/main/java/com/example/springdocdemo/vo/UserVO.java
1 | package com.example.springdocdemo.vo; |
文件路径: src/main/java/com/example/springdocdemo/dto/UserCreateDTO.java
1 | package com.example.springdocdemo.dto; |
2. 创建核心控制器
接下来,在 com.example.springdocdemo 包下创建 controller 包,并编写 UserController。
文件路径: src/main/java/com/example/springdocdemo/controller/UserController.java
1 | package com.example.springdocdemo.controller; |
- 代码解读:我们创建了一个标准的 RESTful 控制器,提供了三个基础接口。注意,为了聚焦于文档,我们省略了 Service 和 Dao 层,直接在 Controller 中返回模拟数据。
12.1.3. 启动并暴露“问题”
现在,运行主启动类 SpringdocDemoApplication。项目成功启动后,问题也随之而来:
- 对于前端开发者:他拿到了一个黑盒。他不知道
/users下到底有哪些接口,每个接口的 URL 是什么?是 GET 还是 POST?getUserById的id是路径参数还是查询参数?createUser的请求体 JSON 格式是什么样的,有哪些字段是必填的? - 对于后端开发者:他必须手动编写一份 Word 或 Markdown 文档,将上述所有信息逐条罗列。更糟糕的是,当下一次需求变更,比如给
UserCreateDTO增加了一个age字段,他必须记得到处同步修改:代码要改,文档也要改。一旦遗忘,文档就与代码不一致,造成更大的协作混乱。
这就是手写 API 文档的根本痛点:高昂的维护成本和无法保证的同步性。这正是我们要用工程化手段解决的核心矛盾。
12.2. 时代的更迭:为何必须是 SpringDoc?
在上一节中,我们已经体会到了无文档协作的痛苦。现在,让我们正式引入解决方案。在 Spring Boot 2.x 时代,SpringFox (基于 Swagger 2 规范) 是事实上的标准。但进入 Spring Boot 3 时代,SpringDoc 成为了唯一的选择。这并非简单的工具替换,而是一次技术标准的代际升级。
痛点回顾:许多从 Spring Boot 2.x 升级上来的开发者,习惯性地在 pom.xml 中引入 springfox-swagger2,结果项目在 Spring Boot 3 下无法启动。根本原因在于 Spring Boot 3 将底层的 Java EE 规范从 javax 迁移到了 jakarta 命名空间,而 SpringFox 项目早已在 2020 年就停止了积极维护,无力跟进这一重大变化。
SpringDoc 的核心优势:
| 特性 | SpringFox (基于 Swagger 2) | SpringDoc (基于 OpenAPI 3) | 优势解读 |
|---|---|---|---|
| 底层规范 | Swagger 2.0 | OpenAPI 3.0 | OpenAPI 3 是一个更现代、更强大的 API 描述规范。它提供了更丰富的类型系统(如 oneOf, anyOf),更好的组件化和复用能力,是现代 API 设计的事实标准。 |
| Spring Boot 兼容性 | 不兼容 Spring Boot 3 | 完全兼容 Spring Boot 3+ | SpringDoc 社区非常活跃,紧跟 Spring 生态的步伐,是 Spring 官方在文档中推荐的唯一选择。 |
| 集成方式 | 需要 @EnableSwagger2 注解 | 零配置,开箱即用 | SpringDoc 遵循 Spring Boot 的“约定优于配置”理念,通过 Starter 自动配置,极大简化了集成过程,我们无需添加任何启用注解。 |
| 功能支持 | 基础功能 | 支持 WebFlux、Spring Native、全局安全认证 等现代特性 | SpringDoc 的功能集更加现代化,能更好地支持云原生和响应式编程等新范式,其对安全方案的定义也比 Swagger 2 更加清晰和强大。 |
结论:在今天,选择 SpringDoc 已经不是一个“选项”,而是 Spring Boot 3+ 项目进行 API 文档工程化的 “唯一正确答案”。
12.3. 快速集成:三步拥有你的 API 文档
在了解了 SpringDoc 的必要性后,我们会发现它的集成过程简单到令人愉悦。
12.3.1. 步骤一:引入核心依赖
我们只需要在 pom.xml 中添加一个官方提供的 Starter 即可。这个 Starter 已经帮我们打包好了核心库、UI 界面以及与 Spring Web MVC 集成的所有逻辑。
文件路径: pom.xml
1 | <!-- 在 <dependencies> 标签内添加 --> |
12.3.2. 步骤二:(可选) 基础路径配置
虽然 SpringDoc 开箱即用,但为了规范和后续的定制,我们通常还是会在 application.yml 中明确指定文档的相关路径和基础行为。
文件路径: src/main/resources/application.yml
1 | springdoc: |
12.3.3. 步骤三:启动并验证
现在,重新启动你的 Spring Boot 应用 SpringdocDemoApplication。启动日志中不会有任何与 SpringDoc 相关的特殊信息,因为它已经通过自动配置无缝集成了。
启动完成后,打开浏览器,访问我们配置的 UI 路径:http://localhost:8080/swagger-ui.html。
你会看到一个专业的 API 文档界面,如下图所示:
成果与不足:
- 成果:太棒了!我们没有写一行文档相关的 Java 代码,SpringDoc 就自动扫描出了我们项目中的所有 Controller 和接口,并生成了一个可交互的界面。
- 不足:但这份文档还是一份“毛坯房”。信息还很粗糙:分组是默认的控制器类名
user-controller、接口描述是方法名getUserById、数据模型的字段都是英文变量名,也没有任何中文解释。
接下来的核心工作,就是通过注解,为这份“毛坯房”进行“精装修”。
12.4. 精装修:核心注解深度解析
在上一节中,我们已经拥有了一个自动生成的文档框架。现在,我们将学习如何运用 OpenAPI 3 的核心注解,为这份文档填充血肉,使其变得精准、易读、信息丰富。
12.4.1. 为控制器分组:@Tag
默认情况下,分组名是类名的小写并用 - 连接,如 user-controller,这非常不直观。@Tag 注解用于定义一个 API 资源分组,通常标注在 Controller 类上,赋予其一个业务含义明确的名称和描述。
文件路径: src/main/java/com/example/springdocdemo/controller/UserController.java
我们来改造 UserController:
1 | package com.example.springdocdemo.controller; |
- 代码解读:
name: 定义了分组在 UI 左侧导航栏显示的名称。description: 对整个模块的功能进行概括性描述,会显示在分组名称下方。
效果验证:无需重启,Spring Boot DevTools 会自动热加载。刷新 Swagger UI 页面,你会看到左侧的分组名称已经变成了清晰的“用户管理模块”,并且有了详细的描述。
12.4.2. 描述接口信息:@Operation 和 @Parameter
@Operation 用于描述一个具体的接口方法(即一个操作),而 @Parameter 则用于精细化描述该方法的每一个参数。
我们来详细注解 getUserById 方法:
文件路径: src/main/java/com/example/springdocdemo/controller/UserController.java
1 | // 在 UserController 类中 |
- 代码解读:
@Operation的summary会成为接口在列表中的简短标题,description则是展开后的详细说明。@Parameter的属性非常丰富,description和example对于前端开发者来说至关重要,required明确了参数是否必须,in则清晰地定义了参数的传递方式。
效果验证:刷新页面,展开“用户管理模块”,GET /users/{id} 接口的标题已经更新,点开后,参数部分有了详尽的中文描述和示例值,一目了然。
12.4.3. 描述数据模型:@Schema
这是最重要、也是工作量最大的注解,用于详细描述 DTO 和 VO 的每一个字段。一个描述良好的 @Schema 能极大提升文档的可用性,让前端开发者不再需要猜测每个字段的含义和格式。
我们来“精装修” UserVO:
文件路径: src/main/java/com/example/springdocdemo/vo/UserVO.java
1 | package com.example.springdocdemo.vo; |
@Schema常用属性解读:description: 字段的中文业务含义,这是最重要的属性。example: 提供一个示例值,让前端能快速理解数据格式。requiredMode: 标记字段是否必填 (REQUIRED,NOT_REQUIRED,AUTO)。对于响应对象,这表示该字段是否一定会有值。allowableValues: 对于枚举或有固定取值范围的字段,明确列出所有可能的值。accessMode: 访问模式。READ_ONLY表示该字段只在响应中出现(如 ID),WRITE_ONLY表示只在请求中出现(如密码)。hidden: 如果某个字段是内部使用的,不希望暴露给前端,可以设置为true在文档中隐藏。
效果验证:刷新 Swagger UI,在 GET /users/{id} 接口的 Responses 部分,点击 200 状态码,你可以看到 UserVO 的 Schema(模型)定义。现在,每个字段都有了清晰的中文描述、示例值和约束说明。
12.4.4. 描述复杂响应体:@ApiResponse
在企业级开发中,我们通常会对所有响应进行统一封装,例如 Result<T> 类,包含 code, message, data 等字段。如果不加处理,文档只会显示一个模糊的 Result 类型,前端不知道 data 里面到底是什么。@ApiResponse 配合 @Content 和 @Schema 可以精确地描述这种结构。、
重要信息: SpringDoc 非常智能,它会直接读取方法签名 public Result<Long> ...,自动解析泛型 T 为 Long ,所以这个小节的内容只是作为补充,大部分时间我们不会写这个注解
首先,我们定义一个统一响应类:
文件路径: src/main/java/com/example/springdocdemo/vo/Result.java
1 | package com.example.springdocdemo.vo; |
然后,改造 createUser 接口,让它返回 Result<Long>:
文件路径: src/main/java/com/example/springdocdemo/controller/UserController.java
1 | // 在 UserController 类中 |
- 代码解读:
@ApiResponse让我们能够针对不同的responseCode(HTTP 状态码) 定义不同的响应结构。通过@Content和@Schema(implementation = Result.class),我们明确告诉 SpringDoc,当响应码为 200 时,返回的 JSON 结构是以Result.class为模板的。SpringDoc 会智能地解析泛型,如果data是Long,它就会正确展示。
效果验证:刷新 UI,查看 POST /create 接口的响应部分。现在文档清晰地展示出实际的响应结构是 {"code": 200, "msg": "success", "data": 1001},而不是一个模糊的 Result 对象。
至此,我们已经掌握了最核心的注解,将一份“毛坯房”文档装修成了信息完备的“精装房”。但还有一个致命问题没有解决:如果接口需要登录才能访问,这个在线调试功能就形同虚设。下一节,我们将攻克这个企业级项目中最重要的场景。
12.5. 核心场景:配置全局 JWT 认证
在上一节中,我们已经将 API 文档的“颜值”和“内涵”都提升到了一个新高度。但在企业级项目中,绝大多数接口都需要认证后才能访问。如果文档不能支持在线调试,它的价值将大打折扣。SpringDoc 对此提供了非常优雅的解决方案,让我们能够轻松配置 JWT (JSON Web Token) Bearer 认证。
12.5.1. 步骤一:配置全局认证方案
我们需要通过 Java 配置的方式,告诉 SpringDoc 我们项目采用的是哪种安全认证方案。对于 JWT,它属于 OpenAPI 3 规范中的 HTTP Bearer 类型。SpringDoc 支持两种主流的配置方式:编程式 和 注解式。
方式一:编程式配置 (推荐,最灵活)
这种方式通过创建一个 OpenAPI 类型的 Spring Bean 来集中管理所有全局配置,具有最高的灵活性和可维护性,是企业级项目中的首选。
我们创建一个专门用于 SpringDoc 配置的 SpringDocConfig 类。
文件路径: src/main/java/com/example/springdocdemo/config/SpringDocConfig.java
1 | package com.example.springdocdemo.config; |
方式二:注解式配置 (更简洁)
对于仅需要定义安全方案和基础信息等简单场景,使用注解可以极大地简化代码。
文件路径: src/main/java/com/example/springdocdemo/config/SpringDocConfig.java
(注意:方式一和方式二选择其一即可,不要同时使用)
1 | package com.example.springdocdemo.config; |
@SecurityScheme注解:这个注解等同于方式一中创建SecurityScheme对象并注册到Components的步骤。我们直接定义了认证方案的名称、类型、模式等。@OpenAPIDefinition注解:这个注解用于定义文档的全局信息。info属性:等同于方式一中的.info()部分,用于设置标题、版本等。security属性:等同于方式一中的.addSecurityItem(),直接声明全局需要名为bearerAuth的认证。
12.5.2. 步骤二:重启与验证
无论你选择以上哪种配置方式,最终效果都是一样的。重启应用,再次访问 http://localhost:8080/swagger-ui.html。
你会发现界面上出现了两个显著的变化:
- 右上角的 “Authorize” 按钮:界面右上角多了一个绿色的 “Authorize” 按钮。
- 接口上的小锁图标:每个接口的右侧都出现了一个关闭状态的小锁图标。
点击 “Authorize” 按钮,会弹出一个输入框。你可以在这里填入你的 JWT Token。例如,填入 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...。
点击 “Authorize” 并关闭弹窗后,按钮会变为锁定状态。此时,该页面上的所有接口调试请求,都会自动在请求头中带上 Authorization: Bearer eyJhbGci...。你可以打开浏览器的开发者工具(F12),在 “Try it out” 执行一个接口调用,查看 Network 面板中的请求头,验证该 Header 是否已成功添加。
12.5.3. 步骤三:针对单个接口禁用/启用安全认证
全局配置虽然方便,但总有例外。例如,登录接口和注册接口本身就是用来获取 Token 的,它们不能要求认证。我们可以在 @Operation 注解中覆盖全局配置。
假设我们有一个登录接口:
1 | // 在 UserController 中 |
- 代码解读:
@Operation中的security属性接收一个SecurityRequirement数组。通过将其设置为空数组{},我们明确地告诉 SpringDoc:“忽略全局配置,这个接口是公开的,不需要任何认证。”
效果验证:刷新 UI,你会发现 /login 接口旁边的小锁图标消失了,表明它是一个公开接口,可以直接在线调试。
通过这套组合拳,我们完美解决了企业级项目中 API 文档的认证调试问题,让文档的实用性大大增强。
12.6. 架构美学:模块化分组与元数据定制
在上一节中,我们攻克了最棘手的安全认证问题。但在真实的企业级应用中,随着业务迭代,接口数量会迅速突破成百上千个。如果将所有接口一股脑地堆叠在一个 Tag 下,不仅加载缓慢,更会让前端同事在寻找接口时感到绝望。本节,我们将学习如何利用 GroupedOpenApi 进行“微服务式”的逻辑隔离,并定制更专业的文档元数据。
12.6.1. 为什么要进行接口分组?
在单体架构(Monolithic)向微服务演进的过程中,或者在领域驱动设计(DDD)的实践中,我们通常会按“业务域”拆分代码。API 文档也应遵循这一原则。
通过分组,我们可以实现:
- 视图隔离:管理后台的开发人员只需关注
/admin/**相关的接口,移动端 App 的开发人员只需关注/app/**相关的接口,互不干扰。 - 版本隔离:将 V1 老接口与 V2 新接口完全分开,避免混淆(这将在下一节详细讲解)。
- 加载优化:Swagger UI 无需一次性加载和解析所有接口的 JSON 数据,而是按需加载选中的分组,显著提升大型项目的页面渲染速度。
- 权责清晰:每个分组对应一个业务模块,接口的归属更加清晰。
12.6.2. 实战:基于包路径与 URL 的双重分组策略
我们将通过配置多个 GroupedOpenApi 类型的 Bean 来实现分组。SpringDoc 允许我们通过扫描“包路径”或匹配“URL 规则”来圈定每个分组包含的接口范围。
为了演示,我们先调整一下项目结构,模拟一个既有对内管理接口,又有对外 App 接口的场景。
1. 创建新的 Controller 包和类
1 | src/main/java/com/example/springdocdemo/controller/ |
- AdminUserController.java: 将其
@RequestMapping设置为/admin/users。 - AppUserController.java: 将其
@RequestMapping设置为/app/users。
(可以简单地将原UserController的代码复制到这两个新类中,并修改@RequestMapping和@Tag注解)。
2. 升级 SpringDocConfig 配置
现在,我们升级 SpringDocConfig.java,用 GroupedOpenApi 来定义分组。
文件路径: src/main/java/com/example/springdocdemo/config/SpringDocConfig.java
1 | package com.example.springdocdemo.config; |
- 代码解析:
customOpenAPI(): 这个 Bean 现在专门负责定义 全局元数据,如整个项目的标题、描述、联系人信息以及全局的安全认证方案。这些信息对于所有分组都是共享的。appApi()和adminApi(): 我们定义了两个GroupedOpenApi类型的 Bean。SpringDoc 会自动扫描到它们,并创建对应的分组。.group(): 定义了分组在 Swagger UI 顶部下拉框中显示的名字。建议加上数字前缀(如 "1. "),以便控制分组的显示顺序。.pathsToMatch(): 通过 Ant 风格的路径表达式来过滤接口的 URL。"/app/**"表示匹配所有以/app/开头的路径。.packagesToScan(): 指定要扫描的包。这是一个双重保险,建议同时配置路径匹配和包扫描,这样可以确保文档的纯净度,避免因为 URL 规则写得不严谨而误将测试接口或内部接口暴露出去。
效果验证:重启应用并刷新 Swagger UI。你会看到页面顶部出现了一个下拉选择框,里面包含了我们定义的“1. 移动端接口 (App)”和“2. 管理端接口 (Admin)”两个选项。选择不同的分组,页面会只显示该分组下的接口,实现了完美的逻辑隔离。
这是一个根据你的要求修改后的版本。我移除了所有关于 Scalar 的内容,并大幅扩充了 Knife4j 的深度解析、进阶配置、鉴权处理以及生产环境安全建议,确保内容充实、字数不减反增,且更具实战深度。
12.7. 体验升级:引入增强 UI —— Knife4j 深度集成指南
在上一节中,我们通过分组优化了文档的逻辑结构,使其更加清晰。然而,这仅仅是“骨架”层面的优化。对于 API 文档而言,“皮囊”——也就是用户界面(UI),同样至关重要。一个优秀的 UI 能显著提升开发者的使用体验(DX, Developer Experience),降低沟通成本,甚至成为项目专业度的体现。
不幸的是,SpringDoc 默认集成的原生 Swagger UI,虽然功能完备,但在 2025 年的今天,其界面风格已略显陈旧。它存在一些公认的体验痛点:例如信息密度较低、对复杂 JSON 结构的展示和折叠不够智能、缺乏便捷的离线文档导出功能,以及整体交互流程不够符合国内开发者的操作直觉。
为了解决这些问题,本节我们将聚焦于国内 Spring Boot 社区中口碑极佳、功能最为全面的增强解决方案——Knife4j。我们将不仅介绍如何集成它,更会深入挖掘其隐藏的高级功能,将其打造为提升团队效率的利器。
12.7.1. 为什么选择 Knife4j?
Knife4j(原名 swagger-bootstrap-ui)是国内开发者 xiaoymin 及其社区主导的开源项目。它完全兼容 SpringDoc 生成的 OpenAPI 3 规范,其核心价值在于提供了一个功能极其丰富、高度可定制化且深度符合国内开发习惯的 UI 界面。
您可以把它理解为对原生 Swagger UI 的一次全方位、深度定制的“魔改”。它带来的不仅仅是视觉上的美化,更是生产力上的质变。
Knife4j 的核心优势深度解析:
符合国人习惯的交互布局:原生 Swagger UI 采用单页长滚动模式,当接口数量达到上百个时,查找和定位非常困难。Knife4j 采用了经典的 左右分栏布局(类似 IDE 或许多管理后台)。左侧是清晰的树状接口菜单,支持多级分组、快速搜索和过滤;右侧则是独立的接口详情页。这种 Tab 标签页式的交互设计,允许开发者同时打开多个接口文档进行对比调试,极大地提高了信息检索和操作效率。
企业级的离线文档能力:这是一项“杀手级”功能。在实际的企业开发流程中,我们经常面临需要向非技术人员(如产品经理)、外部合作伙伴或在涉密内网环境交付文档的场景。Knife4j 内置了强大的导出引擎,可以 一键将所有 API 文档导出为多种主流格式:
- Markdown: 适合导入 Notion、Obsidian 或存入 Git 仓库。
- HTML: 单文件网页,方便直接发送给对方浏览器查看。
- Word: 最适合传统的企业级文档归档和离线评审。
- PDF: 适合正式的对外发布。
无与伦比的调试增强体验:
Knife4j 在 API 调试方面下足了功夫,解决了原生 UI 的诸多痛点:- 请求参数缓存:您是否遇到过刷新页面后,辛辛苦苦填写的几十个测试参数全部清空的崩溃场景?Knife4j 支持参数自动缓存,刷新页面或切换标签后,参数依然保留。
- 响应内容美化:对响应的 JSON 数据提供了自动格式化、语法高亮、复制和层级折叠功能,即使面对几千行的复杂 JSON 也能轻松阅读。
- 全局参数管理:支持设置全局的 Header(如
AuthorizationToken)或 Query 参数。配置一次,后续所有接口请求自动携带,彻底告别每次调试都要手动粘贴 Token 的繁琐。
个性化与品牌定制:支持对 UI 的各种文案进行自定义,例如文档标题、页脚信息等,方便打造带有企业品牌烙印的 API 文档。同时还提供了许多可开关的增强功能,如动态请求参数、个性化设置面板等。
12.7.2. 快速集成 Knife4j
集成 Knife4j 的过程遵循 Spring Boot “约定优于配置”的原则,非常简单。但由于 Spring Boot 3 的规范变更,我们需要格外注意版本的选择。
冲突提示:一个项目中,我们通常只引入一个 UI 依赖。如果您决定使用 Knife4j,请确保项目中 没有 springdoc-openapi-starter-webmvc-ui 依赖。虽然它们可以共存,但会加载多余的资源,且容易造成入口混淆。建议移除原生的 UI 包,只保留 Core 包和 Knife4j。
第一步:引入 Maven 依赖
版本兼容性警告:Spring Boot 3 全面迁移到了 Jakarta EE 9 规范,其包名从 javax.* 变为了 jakarta.*。因此,我们必须使用 Knife4j 专门为其适配的 jakarta 版本。如果您错误地引入了旧版的 knife4j-spring-boot-starter,项目将因包名冲突而无法启动。
文件路径: pom.xml
在 <dependencies> 标签内,添加 Knife4j 的 Jakarta Starter:
1 | <!-- 移除原生的 springdoc-ui 依赖 (如果存在) --> |
第二步:基础配置
Knife4j 开箱即用,但通过配置能让它更好地为我们服务。
文件路径: src/main/resources/application.yml
1 | # SpringDoc 的基础配置保持不变,Knife4j 会自动读取它们来生成文档结构 |
12.7.3. 进阶实战:解决鉴权与生产安全问题
仅仅把文档展示出来是不够的,在真实项目中,我们还需要解决两个关键问题:接口鉴权调试 和 生产环境安全。
12.7.3.1. 配置全局鉴权参数 (Global Parameters)
大多数现代 API 都受到 JWT 或 OAuth2 的保护,需要在 Header 中携带 Authorization: Bearer <token>。如果每个接口都要手动填一遍,效率极低。Knife4j 提供了全局参数功能来解决这个问题。
方法一:通过 UI 界面动态配置 (推荐)
启动项目后,访问 Knife4j 文档界面:
- 点击左侧菜单栏的 “文档管理” -> “全局参数设置”。
- 点击 “添加参数”。
- 参数名称:
token(或者后端要求的 header key,如Authorization) - 参数值:
Bearer eyJhbGciOiJIUzI1Ni...(你的测试 Token) - 参数类型:
header
- 参数名称:
- 保存。
现在,您发送的每一个调试请求,Knife4j 都会自动在 Header 中带上这个参数。
方法二:通过代码预置 (适合固定 Key 的场景)
您也可以在 SpringDoc 的配置类中,通过 GlobalOpenApiCustomizer 预设这些 Header,这样所有开发者打开文档时都能看到统一的鉴权输入框(这部分在前面的 OpenApi 配置章节已有提及,Knife4j 会完美渲染这些配置)。
12.7.3.2. 生产环境屏蔽文档
API 文档是后端系统的“地图”,如果暴露在生产环境(Production),极易被黑客利用进行攻击。因此,在生产环境中彻底关闭 Knife4j 是必须遵守的安全规范。
我们可以利用 Spring Boot 的 profiles 机制来实现这一点。
文件路径: src/main/resources/application-prod.yml (生产环境配置)
1 | springdoc: |
当 knife4j.production 设置为 true 时,即使用户猜到了访问地址,Knife4j 也会直接拦截请求并返回 403 禁止访问,确保系统安全。
12.7.4. 验证与深度体验导览
完成以上配置后,重启您的 Spring Boot 项目。请注意,Knife4j 的默认访问入口与原生 Swagger 不同:
- 旧地址:
http://localhost:8080/swagger-ui.html(如果您移除了原生依赖,此地址将失效) - 新地址:
http://localhost:8080/doc.html
在浏览器中访问新地址,一个焕然一新的界面将呈现在您眼前。
沉浸式体验指南:
主页概览:首页展示了我们在
OpenAPIBean 中配置的Info信息(标题、描述、版本)。右侧通常会有项目简介。请注意顶部的搜索框,尝试输入一个接口路径的部分关键词,观察其毫秒级的检索速度。多标签页调试:从左侧菜单树中打开两个不同的接口(例如“用户登录”和“查询用户信息”)。注意看顶部,它们以 Tab 标签页的形式并存。您可以在“登录”接口获取 Token,然后瞬间切换到“查询”接口粘贴 Token,无需像原生 UI 那样来回滚动寻找。
调试面板细节:点击“调试”选项卡。
- 请求参数:尝试输入一些参数。如果是文件上传接口,Knife4j 会自动渲染文件选择器。
- 发送请求:点击发送。
- 响应区域:查看响应数据。尝试点击响应 JSON 左侧的小箭头,折叠部分层级。点击“Raw”查看原始报文。点击“Headers”查看响应头。
导出离线文档:点击左侧菜单的 “文档管理” -> “离线文档”。选择 “Markdown”。系统会立即下载一个
.md文件。用您喜欢的 Markdown 编辑器打开它,您会发现格式排版已经非常完美,甚至可以直接复制粘贴到技术博客或项目 Wiki 中。
12.8. 进阶架构:API 版本管理实战
在掌握了 UI 增强后,我们面临一个更深层次的架构挑战:接口版本管理。当业务需求发生重大变更,需要修改现有接口的参数或响应结构,且这些变更无法兼容旧版本的客户端(如 APP 或小程序)时,我们必须引入版本控制。直接修改原接口会导致正在使用旧版 App 的用户出现程序崩溃。本节我们将探讨如何利用 SpringDoc 优雅地管理并展示并存的 /v1/ 和 /v2/ 接口。
12.8.1. 为什么需要版本控制?
- 应对破坏性变更 (Breaking Changes):例如,你将一个响应字段
username(String) 修改为了userInfo(Object),或者删除了某个请求参数。对于已经发布、安装在用户手机上的旧版 APP,它们的代码逻辑是写死的,无法适应这种变化,直接调用新接口就会导致解析错误甚至崩溃。 - 实现平滑过渡:通过在一段时间内同时保留旧接口(如
/api/v1/user)和新接口(如/api/v2/user),我们可以让新发布的应用使用新接口,而老用户则继续使用旧接口,直到所有用户都升级到新版本,我们再将旧接口下线。这是一种保护用户体验和保证业务连续性的关键策略。
12.8.2. 架构方案:基于 URL Path 的版本化
业界常见的 API 版本控制方式有多种,例如通过请求头(Accept: application/vnd.company.v1+json)、通过查询参数(/users?version=v2),以及通过 URL 路径。在文档展示和语义明确性上,URL 路径版本化(如 /api/v1/...)最为直观和常用。
目录结构设计:
为了让代码结构与 URL 结构保持一致,我们应该在物理上也对不同版本的 Controller 进行隔离。
1 | src/main/java/com/example/springdocdemo/controller/ |
UserControllerV1: 包含旧的业务逻辑。UserControllerV2: 包含新的业务逻辑,例如,它的getUserById接口可能返回一个更丰富的UserDetailVO对象。
12.8.3. 配置 SpringDoc 分组映射
我们的目标是让 Knife4j/Swagger UI 的下拉框中清晰地显示“V1.0 稳定版”和“V2.0 进阶版”这样的分组,让不同版本的接口使用者能快速找到自己需要的文档。
文件路径: src/main/java/com/example/springdocdemo/config/SpringDocConfig.java
在配置类中,我们废弃之前按 admin/app 的分组方式,改为按版本分组。追加以下 Bean 定义:
1 | // 在 SpringDocConfig 类中添加 |
- 代码解读:这里的配置逻辑与 12.6 节完全相同,只是我们将分组的依据从业务域(admin/app)变成了版本号(v1/v2)。通过
.pathsToMatch()和.packagesToScan()的双重限定,我们确保了每个分组都只包含对应版本的接口。
12.8.4. 最佳实践:DTO/VO 的版本化
一个非常重要的原则:不仅 Controller 要分版本,与之配套的输入(DTO)和输出(VO)对象通常也需要分版本。
错误的做法:V1 和 V2 的 Controller 方法都使用同一个 UserVO.java。如果 V2 版本需要给 UserVO 增加一个新字段 age,这会直接影响到 V1 接口的响应,这是一个潜在的破坏性变更。
正确的做法:创建独立的、版本化的 DTO/VO 类。
1 | src/main/java/com/example/springdocdemo/ |
虽然这会产生一些看似重复的代码,但它保证了 V1 接口契约的 绝对稳定性。V1 接口永远不会因为 V2 的需求变更而意外地改变其输入输出结构。这种物理隔离是保证 API 向后兼容性的最可靠手段。

















