Spring Boot 使用 SpringDoc 构建 OpenAPI 3 接口文档

在 Spring Boot 项目中,接口文档不应该是一份需要人工同步维护的附属 Word 文档,而应该尽可能从真实的 Controller、DTO、参数约束和接口注解中生成。SpringDoc 正是 Spring Boot 生态中常用的 OpenAPI 文档生成方案。本文围绕 SpringDoc 的工作原理、Spring Boot 3/4 版本选择、Swagger UI、OpenAPI 注解、接口分组、JWT 鉴权、WebFlux、生产环境控制以及 Springfox 迁移等内容,整理一套适合实际项目使用的 API 文档方案。

为什么需要 SpringDoc

REST API 开发中有一个长期存在的问题:代码和接口文档很容易失去同步

如果接口文档完全依靠开发人员手工维护,随着项目持续迭代,很容易出现下面这些情况:

  • Controller 已经增加了参数,文档没有更新;
  • DTO 字段已经修改,文档仍然描述旧结构;
  • HTTP 状态码和错误响应发生变化,调用方却不知道;
  • 一个接口究竟需要 Header、Query Parameter 还是 Request Body,只能翻代码;
  • 前端、测试、第三方系统都在使用不同版本的接口说明。

Swagger、OpenAPI 和 SpringDoc 解决的核心问题,就是把 API 的机器可读定义和实际应用代码建立联系。

SpringDoc 会在 Spring Boot 应用运行时分析 Spring 配置、Controller、Java 类型以及相关注解,将这些信息转换成 OpenAPI 描述,并输出 JSON/YAML,再由 Swagger UI 等工具进行可视化。SpringDoc 官方明确说明,它通过运行时分析应用配置、类结构和注解推断 API 语义。

整个过程可以抽象为:

flowchart LR
    A["Spring MVC / WebFlux<br/>Controller"]
    B["DTO / VO / Record"]
    C["Bean Validation<br/>@NotNull / @Size ..."]
    D["Swagger Annotations<br/>@Operation / @Schema ..."]

    E["SpringDoc"]
    F["OpenAPI Model"]

    G["/v3/api-docs<br/>JSON"]
    H["/v3/api-docs.yaml<br/>YAML"]

    I["Swagger UI"]
    J["Scalar"]
    K["代码生成 / 测试 / API Gateway / CI"]

    A --> E
    B --> E
    C --> E
    D --> E

    E --> F

    F --> G
    F --> H

    G --> I
    G --> J
    G --> K
    H --> K

SpringDoc 本身需要和另外几个容易混淆的概念区分开。

名称 作用
OpenAPI Specification 描述 HTTP API 的标准规范
Swagger Annotations Java 中描述 OpenAPI 信息的一组注解
Swagger UI 将 OpenAPI 文档渲染成交互式网页
SpringDoc 从 Spring Boot 应用中自动生成 OpenAPI 描述
Scalar 另一种 OpenAPI 可视化和交互式 API 客户端

所以准确地说,并不是“SpringDoc 生成 Swagger”,而是:

SpringDoc 根据 Spring 应用生成 OpenAPI 文档,Swagger UI 再消费这份 OpenAPI 文档进行展示。

SpringDoc 也并不是 Spring Framework 官方模块,而是一个社区维护项目。

SpringDoc 与 Springfox 的关系

在较早的 Spring Boot 项目中,经常能看到:

1
2
3
4
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
</dependency>

以及:

1
2
3
4
@Api
@ApiOperation
@ApiModel
@ApiModelProperty

这一套属于 Springfox 体系。

在 Spring Boot 3、Spring Framework 6 和 Jakarta EE 命名空间成为主流之后,更常见的方案是:

1
2
3
4
5
6
7
Spring Boot
+
SpringDoc
+
OpenAPI 3
+
Swagger UI

SpringDoc 官方迁移指南也明确要求从 Springfox 迁移时删除 Springfox/Swagger 2 相关依赖,改为 SpringDoc starter,并把 Swagger 2 注解迁移到 io.swagger.v3.oas.annotations 下的 OpenAPI 3 注解。

常见注解对应关系如下:

Springfox / Swagger 2 OpenAPI 3
@Api @Tag
@ApiOperation @Operation
@ApiImplicitParam @Parameter
@ApiImplicitParams @Parameters
@ApiModel @Schema
@ApiModelProperty @Schema
@ApiParam @Parameter
@ApiResponse(code = ...) @ApiResponse(responseCode = ...)
@ApiIgnore @Hidden@Operation(hidden = true)

SpringDoc 官方还指出,如果原来有多个 Springfox Docket,可以使用 GroupedOpenApi 进行替换。

Spring Boot 与 SpringDoc 的版本关系

SpringDoc 是一个与 Spring Boot 底层 Web API 联系比较紧密的组件,因此版本不能随便选

Spring Boot 3 与 Spring Boot 4 分别对应不同的 SpringDoc 主版本:

1
2
3
4
5
6
7
8
Spring Boot 2.x
└── SpringDoc 1.x

Spring Boot 3.x
└── SpringDoc 2.x

Spring Boot 4.x
└── SpringDoc 3.x

SpringDoc v2 官方兼容矩阵给出的历史关系为:

Spring Boot SpringDoc
3.0.x 2.0.x ~ 2.1.x
3.1.x 2.2.x
3.2.x 2.3.x ~ 2.5.x
3.3.x 2.6.x
3.4.x 2.7.x ~ 2.8.x
3.5.x 2.8.x
4.x 3.x

需要注意一个现实中的版本细节。

截至 2026 年 8 月,SpringDoc 已经同时维护 Spring Boot 3 和 Spring Boot 4 两条主要版本线:

  • springdoc-openapi 2.9.0
  • springdoc-openapi 3.1.0

其中 2.9.0 的发布说明已经升级到 Spring Boot 3.5.16,而 3.1.0 对应 Spring Boot 4.x。

因此项目选型时不要机械照抄旧博客中的版本号,例如早期教程中的:

1
<version>2.1.0</version>

或者:

1
<version>2.6.0</version>

这些版本在其发布时间是合理的,但并不意味着今天创建 Spring Boot 3.5 项目时还应该继续使用它们。

更稳妥的规则是:

先确定 Spring Boot 版本,再选择与之匹配的 SpringDoc 主版本和维护版本。

本文后面的 Spring Boot 3 示例以当前 SpringDoc 2.x starter 体系为基础。

Spring Boot 3 快速接入 SpringDoc

Spring MVC 项目

如果项目使用:

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

并且希望同时生成 OpenAPI 文档和 Swagger UI,可以增加:

1
2
3
4
5
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.9.0</version>
</dependency>

SpringDoc 的 starter 已经包含 Swagger UI 所需的依赖,因此不需要再额外添加 Swagger UI。SpringDoc v2 文档给出的默认 OpenAPI JSON 地址是 /v3/api-docs,同时还可以通过 /v3/api-docs.yaml 获取 YAML 格式描述。

启动应用后可以访问:

1
http://localhost:8080/v3/api-docs

获取 OpenAPI JSON:

1
2
3
4
5
6
7
8
{
"openapi": "3.1.0",
"info": {
"title": "..."
},
"paths": {
}
}

YAML:

1
http://localhost:8080/v3/api-docs.yaml

Swagger UI:

1
http://localhost:8080/swagger-ui.html

不同版本和重定向方式下,也经常能看到实际 UI 静态资源入口:

1
http://localhost:8080/swagger-ui/index.html

早期 Spring Boot 3 教程也经常使用 /swagger-ui/index.html 直接访问页面。

只生成 OpenAPI,不提供 Swagger UI

有些服务本身并不需要提供网页文档,只希望:

  • API Gateway 获取 OpenAPI;
  • CI 导出 API Schema;
  • OpenAPI Generator 生成 SDK;
  • API 管理平台统一展示。

这种情况下可以只使用:

1
2
3
4
5
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-api</artifactId>
<version>2.9.0</version>
</dependency>

这个 starter 提供 OpenAPI endpoint,而不包含 Swagger UI。

在微服务生产环境中,这种方式往往比每个服务都部署 Swagger UI 更轻量。

WebFlux 项目如何接入

如果项目使用的是:

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

则不要使用 webmvc starter。

提供 Swagger UI:

1
2
3
4
5
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webflux-ui</artifactId>
<version>2.9.0</version>
</dependency>

只需要 OpenAPI:

1
2
3
4
5
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webflux-api</artifactId>
<version>2.9.0</version>
</dependency>

SpringDoc 对 Spring MVC 和 WebFlux 都提供对应 starter。

这点非常重要:

1
2
3
4
5
Spring MVC
└── springdoc-openapi-starter-webmvc-*

Spring WebFlux
└── springdoc-openapi-starter-webflux-*

项目本身是什么 Web 技术栈,就选择对应模块,不要为了 Swagger UI 随意混入另一个 Web Stack。

最基本的 application.yml 配置

很多项目其实不需要大量配置。

一个比较常见的开发环境配置可以写成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
springdoc:
api-docs:
enabled: true
path: /v3/api-docs

swagger-ui:
enabled: true
path: /swagger-ui.html

packages-to-scan:
- com.example.demo.controller

paths-to-match:
- /api/**

show-actuator: false

SpringDoc 默认:

1
2
3
4
5
OpenAPI JSON:
/v3/api-docs

Swagger UI:
/swagger-ui.html

并允许通过 packages-to-scanpaths-to-matchpaths-to-exclude 等属性控制哪些接口进入 OpenAPI 文档。

例如只扫描业务 API:

1
2
3
4
5
6
7
springdoc:
packages-to-scan:
- com.example.order.controller
- com.example.user.controller

paths-to-match:
- /api/**

排除内部接口:

1
2
3
springdoc:
paths-to-exclude:
- /api/internal/**

对于大型项目,这比“所有 Controller 一股脑全部展示”更容易维护。

SpringDoc 到底扫描了什么

SpringDoc 并不是简单扫描几个 Swagger 注解。

它会综合分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
Spring Mapping
@GetMapping
@PostMapping
@PutMapping
@DeleteMapping


Controller 方法

├── PathVariable
├── RequestParam
├── RequestHeader
├── RequestBody


Java 参数 / 返回值类型

├── DTO
├── List<T>
├── Page<T>
├── Record
└── 泛型响应对象


Bean Validation

├── @NotNull
├── @Min
├── @Max
└── @Size


Swagger Annotations

├── @Tag
├── @Operation
├── @Parameter
├── @Schema
└── @ApiResponse


OpenAPI Schema

因此 SpringDoc 的最佳使用方式不是:

每个 Java 字段都重新人工描述一遍 Java 类型。

而应该让 SpringDoc 自动推断能够推断的部分,只给它补充业务语义

例如:

1
2
3
4
@GetMapping("/{id}")
public BookDTO findById(@PathVariable Long id) {
return bookService.findById(id);
}

即使完全没有 Swagger 注解,SpringDoc 也能够识别:

  • HTTP Method:GET;
  • Path:/{id}
  • id 是 Path Parameter;
  • 返回对象类型是 BookDTO

注解真正应该补充的是:

1
2
3
4
“这个接口是做什么的”
“这个字段在业务上是什么意思”
“某个状态码为什么会出现”
“参数有哪些业务限制”

最常用的 OpenAPI 注解

@Tag:描述 Controller 或 API 分组

1
2
3
4
5
6
7
8
@RestController
@RequestMapping("/api/books")
@Tag(
name = "Book",
description = "图书管理接口"
)
public class BookController {
}

Swagger UI 通常会按照 Tag 对接口进行分组。

@Operation:描述具体操作

1
2
3
4
5
6
7
8
@Operation(
summary = "根据 ID 查询图书",
description = "根据图书 ID 查询图书的详细信息"
)
@GetMapping("/{id}")
public BookDTO findById(@PathVariable Long id) {
return bookService.findById(id);
}

其中:

1
summary

适合一句话说明操作用途。

1
description

适合补充更加详细的行为说明。

不要写成:

1
2
3
4
@Operation(
summary = "findById",
description = "findById"
)

这种注解虽然存在,却没有增加任何信息。

@Parameter:描述请求参数

1
2
3
4
5
6
7
8
9
10
11
@GetMapping("/{id}")
public BookDTO findById(
@Parameter(
description = "图书 ID",
required = true,
example = "10001"
)
@PathVariable Long id
) {
return bookService.findById(id);
}

查询参数同样可以描述:

1
2
3
4
5
6
7
8
@GetMapping
public List<BookDTO> list(
@Parameter(description = "书名关键字")
@RequestParam(required = false)
String keyword
) {
return bookService.list(keyword);
}

如果某个参数不应该出现在接口文档中,可以使用:

1
@Parameter(hidden = true)

SpringDoc 也会自动忽略部分 Spring MVC 注入参数,例如 PrincipalLocaleHttpServletRequestHttpServletResponse 等。

@Schema:描述 DTO

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Schema(description = "图书信息")
public class BookDTO {

@Schema(
description = "图书 ID",
example = "10001"
)
private Long id;

@Schema(
description = "图书名称",
example = "Spring in Action"
)
private String name;

@Schema(
description = "作者",
example = "Craig Walls"
)
private String author;

// getter / setter
}

@Schema 可以用于:

  • 类;
  • 字段;
  • 枚举;
  • 方法参数;
  • 请求体模型;
  • 响应模型。

早期 Swagger 2 中:

1
2
@ApiModel
@ApiModelProperty

基本都迁移为 OpenAPI 3 的:

1
@Schema

Bean Validation 与 Schema

可以把 Bean Validation 和 OpenAPI 描述组合起来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Schema(description = "创建图书请求")
public class CreateBookRequest {

@NotBlank
@Size(max = 200)
@Schema(
description = "图书名称",
example = "Spring in Action"
)
private String name;

@NotBlank
@Schema(
description = "作者"
)
private String author;

// getter / setter
}

SpringDoc 支持根据 JSR-303/Bean Validation 的相关约束生成 Schema 信息,例如 @NotNull@Min@Max@Size 等。

这样:

1
2
@NotBlank
@Size(max = 200)

仍然负责真正的运行时校验

而:

1
@Schema(description = "图书名称")

负责补充接口语义

两者职责不要反过来。

描述响应状态码

一个接口不应该只有:

1
200 OK

例如查询资源时,很可能同时存在:

1
2
3
200 查询成功
404 资源不存在
500 服务内部错误

可以通过 @ApiResponses 描述:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Operation(summary = "根据 ID 查询图书")
@ApiResponses({
@ApiResponse(
responseCode = "200",
description = "查询成功"
),
@ApiResponse(
responseCode = "404",
description = "图书不存在"
)
})
@GetMapping("/{id}")
public BookDTO findById(
@Parameter(description = "图书 ID")
@PathVariable Long id
) {
return bookService.findById(id);
}

如果需要进一步指定 Response Body:

1
2
3
4
5
6
7
8
@ApiResponse(
responseCode = "200",
description = "查询成功",
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = BookDTO.class)
)
)

SpringDoc 还能够和 @ControllerAdvice@ResponseStatus 配合生成通用错误响应。

工程上建议统一设计错误模型,例如:

1
2
3
4
5
6
7
8
9
@Schema(description = "统一错误响应")
public class ErrorResponse {

@Schema(description = "错误码")
private String code;

@Schema(description = "错误信息")
private String message;
}

然后让业务异常、参数异常、权限异常都遵循统一响应结构。

这样生成出来的 OpenAPI 才真正具有消费价值。

一个相对完整的 Controller 示例

把前面的内容组合起来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
@RestController
@RequestMapping("/api/books")
@Tag(
name = "Book",
description = "图书管理"
)
public class BookController {

private final BookService bookService;

public BookController(BookService bookService) {
this.bookService = bookService;
}

@Operation(
summary = "根据 ID 查询图书",
description = "返回指定图书的详细信息"
)
@ApiResponses({
@ApiResponse(
responseCode = "200",
description = "查询成功",
content = @Content(
mediaType = "application/json",
schema = @Schema(
implementation = BookDTO.class
)
)
),
@ApiResponse(
responseCode = "404",
description = "图书不存在"
)
})
@GetMapping("/{id}")
public BookDTO findById(
@Parameter(
description = "图书 ID",
required = true,
example = "10001"
)
@PathVariable Long id
) {
return bookService.findById(id);
}

@Operation(summary = "创建图书")
@PostMapping
public BookDTO create(
@Valid
@RequestBody
CreateBookRequest request
) {
return bookService.create(request);
}
}

这里有一个很重要的设计思想:

Spring MVC 注解描述 HTTP 行为,Bean Validation 描述约束,OpenAPI 注解补充接口语义。

不要试图让 Swagger 注解代替真正的接口设计。

配置 API 基本信息

如果什么都不配置,SpringDoc 仍然能够生成文档。

但实际项目通常需要设置:

  • API 名称;
  • API 版本;
  • 描述;
  • 联系信息;
  • License;
  • Server;
  • Security Scheme。

可以创建一个 OpenAPI Bean:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Configuration
public class OpenApiConfig {

@Bean
public OpenAPI openAPI() {
return new OpenAPI()
.info(
new Info()
.title("Book Service API")
.description("图书服务 REST API")
.version("v1")
);
}
}

SpringDoc 官方支持通过 OpenAPI Bean 定制整个 OpenAPI Model,并建议将 @OpenAPIDefinition@SecurityScheme 等定义放在 Spring 管理的 Bean 中。

也可以使用注解:

1
2
3
4
5
6
7
8
9
10
@Configuration
@OpenAPIDefinition(
info = @Info(
title = "Book Service API",
version = "v1",
description = "图书服务 REST API"
)
)
public class OpenApiConfig {
}

两种方式没有必要同时大量使用。

如果需要动态组合配置、统一添加 Security Scheme、Server 或 Components,使用 OpenAPI Bean 通常更加灵活。

Pageable 参数为什么需要 @ParameterObject

例如 Spring Data 中经常有这样的接口:

1
2
3
4
@GetMapping
public Page<BookDTO> page(Pageable pageable) {
return bookService.page(pageable);
}

为了让分页参数清晰地展开为:

1
2
3
page
size
sort

可以使用:

1
2
3
4
5
6
@GetMapping
public Page<BookDTO> page(
@ParameterObject Pageable pageable
) {
return bookService.page(pageable);
}

SpringDoc 从 v1.6.0 开始已经内置了 Pageable 支持。

对于普通查询 DTO 同样可以:

1
2
3
4
5
6
@GetMapping
public List<BookDTO> search(
@ParameterObject BookQuery query
) {
return bookService.search(query);
}

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class BookQuery {

private String keyword;

private String author;

public String getKeyword() {
return keyword;
}

public String getAuthor() {
return author;
}
}

SpringDoc 官方 FAQ 特别说明,@ParameterObject 会把对象字段展开为独立请求参数,但它不支持嵌套 Parameter Object,并且依赖标准 getter 读取属性。

所以对于:

1
GET /api/books?keyword=spring&author=xxx

@ParameterObject 很适合;

但对于复杂嵌套结构,还是应该重新设计查询模型或者使用 Request Body。

使用 GroupedOpenApi 拆分大型接口文档

当系统接口越来越多时,把数百个接口全部放在一个 Swagger UI 中会越来越难找。

例如:

1
2
3
/api/public/**
/api/admin/**
/api/internal/**

可以通过 GroupedOpenApi 分组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Configuration
public class OpenApiGroupConfig {

@Bean
public GroupedOpenApi publicApi() {
return GroupedOpenApi.builder()
.group("public")
.pathsToMatch("/api/public/**")
.build();
}

@Bean
public GroupedOpenApi adminApi() {
return GroupedOpenApi.builder()
.group("admin")
.pathsToMatch("/api/admin/**")
.build();
}
}

生成后通常可以访问:

1
2
/v3/api-docs/public
/v3/api-docs/admin

SpringDoc 官方规定,每一个 group 都需要唯一的 groupName,并可以按照 Path 和 Package 进行筛选。

除了路径,还可以:

1
2
3
4
5
6
7
8
9
@Bean
public GroupedOpenApi userApi() {
return GroupedOpenApi.builder()
.group("user")
.packagesToScan(
"com.example.user.controller"
)
.build();
}

或者组合:

1
2
3
4
5
6
7
8
9
10
@Bean
public GroupedOpenApi orderApi() {
return GroupedOpenApi.builder()
.group("order")
.packagesToScan(
"com.example.order.controller"
)
.pathsToMatch("/api/orders/**")
.build();
}

如果不希望写 Java Bean,还可以通过配置创建 group:

1
2
3
4
5
6
7
8
9
10
11
12
13
springdoc:
group-configs:
- group: user
packages-to-scan:
- com.example.user.controller
paths-to-match:
- /api/users/**

- group: order
packages-to-scan:
- com.example.order.controller
paths-to-match:
- /api/orders/**

SpringDoc 支持直接使用 springdoc.group-configs 定义组,此时不需要额外声明 GroupedOpenApi Bean。

对于模块化单体或者微服务,推荐优先按照业务域而不是 Controller 类名进行分组。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
IAM
├── User
├── Role
└── Permission

Order
├── Order
└── Payment

Inventory
├── Product
└── Stock

这样的文档结构比:

1
2
3
4
UserController
RoleController
PermissionController
...

更能反映真实业务边界。

Spring Security + JWT 接入 Swagger UI

真实系统通常都会有认证。

例如:

1
Authorization: Bearer <JWT>

这里不要给每一个接口都手工增加:

1
2
3
4
@Parameter(
name = "Authorization",
in = ParameterIn.HEADER
)

OpenAPI 对 Authorization 有专门的 Security Scheme 模型。SpringDoc 官方也明确建议通过 SecurityRequirementSecurityScheme 描述 Bearer Token。

可以配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Configuration
public class OpenApiConfig {

@Bean
public OpenAPI openAPI() {
String securitySchemeName = "bearerAuth";

return new OpenAPI()
.info(
new Info()
.title("Book Service API")
.version("v1")
)
.components(
new Components()
.addSecuritySchemes(
securitySchemeName,
new SecurityScheme()
.type(SecurityScheme.Type.HTTP)
.scheme("bearer")
.bearerFormat("JWT")
)
);
}
}

然后在需要认证的接口上:

1
2
3
4
5
6
7
8
9
10
11
12
@Operation(
summary = "查询当前用户的图书",
security = {
@SecurityRequirement(
name = "bearerAuth"
)
}
)
@GetMapping("/mine")
public List<BookDTO> mine() {
return bookService.findMyBooks();
}

也可以通过:

1
2
new SecurityRequirement()
.addList("bearerAuth")

把认证要求设置成全局规则。

但如果系统同时存在:

1
2
3
/login
/public/**
/health

这类匿名接口,就不要简单把所有 API 全局标记为必须 JWT。

Spring Security 需要放行 Swagger 路径

即使 SpringDoc 成功生成了文档,如果 Spring Security 把文档接口全部拦截,浏览器仍然无法正常打开 Swagger UI。

Spring Security 6 常见配置为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Bean
SecurityFilterChain securityFilterChain(
HttpSecurity http
) throws Exception {

http.authorizeHttpRequests(auth -> auth
.requestMatchers(
"/v3/api-docs/**",
"/swagger-ui/**",
"/swagger-ui.html"
)
.permitAll()
.anyRequest()
.authenticated()
);

return http.build();
}

开发和测试环境这样做通常比较方便。

但生产环境是否应该开放 Swagger,需要重新评估,而不是机械 permitAll()

Swagger UI 常用配置

Swagger UI 自己拥有大量配置项,SpringDoc 可以通过:

1
springdoc.swagger-ui.*

直接映射这些配置。

例如:

1
2
3
4
5
6
7
springdoc:
swagger-ui:
path: /swagger-ui.html
operations-sorter: method
tags-sorter: alpha
doc-expansion: none
filter: true

operations-sorter

1
2
3
springdoc:
swagger-ui:
operations-sorter: method

可以按照 HTTP Method 排序。

也可以:

1
2
3
springdoc:
swagger-ui:
operations-sorter: alpha

按照路径字母排序。

tags-sorter

1
2
3
springdoc:
swagger-ui:
tags-sorter: alpha

让 Tag 按字母排序。

大型系统如果不指定排序方式,随着接口增长,Swagger UI 很容易显得杂乱。

doc-expansion

1
2
3
springdoc:
swagger-ui:
doc-expansion: none

默认折叠所有接口。

接口几十个甚至几百个以后,这个配置非常实用。

filter

1
2
3
springdoc:
swagger-ui:
filter: true

Swagger UI 页面会提供过滤输入框,可按 Tag 等信息查找接口。

persist-authorization

1
2
3
springdoc:
swagger-ui:
persist-authorization: true

允许 Swagger UI 保存认证信息,刷新页面后不需要再次输入 Token。默认值为 false

开发环境比较方便,但在公共电脑或共享浏览器中需要谨慎启用。

OpenAPI 3.0 与 3.1

现代 SpringDoc 版本已经支持 OpenAPI 3.1。

在 SpringDoc 2.9.0 的配置中:

1
springdoc.api-docs.version

默认值是:

1
openapi_3_1

因此打开:

1
/v3/api-docs

可能看到:

1
2
3
{
"openapi": "3.1.0"
}

这点对后续工具链很重要。

例如:

1
2
3
4
5
6
7
8
9
10
SpringDoc


OpenAPI

├── Swagger UI
├── API Gateway
├── OpenAPI Generator
├── TypeScript SDK Generator
└── 测试平台

如果后续某个工具仍然只完整支持 OpenAPI 3.0,就要检查整个链路,而不能只确认 Swagger UI 页面“看起来正常”。

API 文档已经不只是页面,它本质上是一个机器可消费的契约文件

Swagger UI 之外:Scalar

现在 SpringDoc 还支持 Scalar。

SpringDoc v2 提供:

1
2
3
4
5
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-scalar</artifactId>
<version>2.9.0</version>
</dependency>

WebFlux 则是:

1
2
3
4
5
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webflux-scalar</artifactId>
<version>2.9.0</version>
</dependency>

默认入口为:

1
/scalar

它和 Swagger UI 的关系不是:

1
Swagger UI + Scalar → 两份 API

而是:

1
2
3
              ┌─ Swagger UI
OpenAPI Spec ─┤
└─ Scalar

两者只是对同一份 OpenAPI Specification 的不同展示和交互方式。SpringDoc v2/v3 官方文档已经同时列出了 Swagger UI 和 Scalar 支持。

Actuator 与独立管理端口

很多生产系统会把:

1
业务端口

和:

1
Management / Actuator 端口

分开。

例如:

1
2
3
4
5
6
server:
port: 8080

management:
server:
port: 9090

SpringDoc 支持把 OpenAPI 和 Swagger UI 暴露在 management port:

1
2
3
4
5
6
7
8
9
10
springdoc:
use-management-port: true

management:
endpoints:
web:
exposure:
include:
- openapi
- swagger-ui

这时可以通过类似:

1
2
http://localhost:9090/actuator/openapi
http://localhost:9090/actuator/swagger-ui

访问文档。

这种架构特别适合:

1
2
3
4
5
6
7
8
9
10
11
12
Internet


API Gateway


8080 业务接口

内部运维网络


9090 Actuator / OpenAPI

这样就不需要为了接口文档,把 Swagger UI 直接暴露给公网。

生产环境是否应该开启 Swagger

默认情况下:

1
2
springdoc.api-docs.enabled = true
springdoc.swagger-ui.enabled = true

SpringDoc 官方分别提供了关闭 API Docs 和 Swagger UI 的配置。

生产环境可以使用:

1
2
3
4
5
6
springdoc:
api-docs:
enabled: false

swagger-ui:
enabled: false

例如:

1
2
3
4
application.yml
application-dev.yml
application-test.yml
application-prod.yml

开发环境:

1
2
3
4
5
6
7
8
# application-dev.yml

springdoc:
api-docs:
enabled: true

swagger-ui:
enabled: true

生产环境:

1
2
3
4
5
6
7
8
# application-prod.yml

springdoc:
api-docs:
enabled: false

swagger-ui:
enabled: false

这里需要区分两件事情:

1
springdoc.api-docs.enabled

控制 OpenAPI JSON/YAML。

1
springdoc.swagger-ui.enabled

控制 UI。

如果:

1
2
3
4
5
6
springdoc:
api-docs:
enabled: false

swagger-ui:
enabled: true

虽然 Swagger UI 页面资源可能存在,但它没有 OpenAPI Specification 可以读取,自然无法正常展示接口。早期 Spring Boot 3 实践文档也特别强调 Swagger UI 依赖 OpenAPI Docs endpoint。

生产环境到底采取哪种方案,建议根据系统情况选择:

1
2
3
4
5
6
7
8
9
10
11
12
13
方案一
关闭 Swagger UI
关闭 /v3/api-docs

方案二
关闭 Swagger UI
保留 /v3/api-docs,仅内部网络访问

方案三
放到 Actuator Management Port

方案四
统一由 API Gateway / API Portal 汇总 OpenAPI

对于微服务系统,通常没有必要让几十个微服务各自在公网暴露 Swagger UI。

微服务中的接口文档聚合

如果系统包含:

1
2
3
4
user-service
order-service
payment-service
inventory-service

每个服务都可以生成自己的:

1
/v3/api-docs

然后由统一文档门户进行聚合:

flowchart LR
    A["user-service<br/>/v3/api-docs"]
    B["order-service<br/>/v3/api-docs"]
    C["payment-service<br/>/v3/api-docs"]
    D["inventory-service<br/>/v3/api-docs"]

    E["API Gateway / Doc Portal"]

    F["统一 API 文档"]

    A --> E
    B --> E
    C --> E
    D --> E

    E --> F

Swagger UI 本身也支持配置多个外部 OpenAPI URL,SpringDoc 提供 springdoc.swagger-ui.urls.* 用于聚合外部 Specification。

这种情况下,服务本身真正应该维护的是:

1
OpenAPI Contract

而不是:

1
Swagger UI 页面

这也是理解 SpringDoc 时一个很关键的转变。

Spring MVC Functional Endpoint 与 WebFlux Functional Endpoint

SpringDoc 不只支持:

1
@RestController

也支持 Spring MVC/WebFlux Functional Endpoint。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Bean
RouterFunction<?> routes() {
return route()
.GET(
"/books",
request -> ServerResponse.ok().build(),
ops -> ops
.operationId("listBooks")
.response(
responseBuilder()
.responseCode("200")
.description("查询成功")
)
)
.build();
}

也可以使用:

1
@RouterOperation

或:

1
@RouterOperations

描述 Functional Endpoint。

SpringDoc 官方提醒,@RouterOperation 场景中 operationId 非常重要,因为框架需要准确定位对应 Route。

所以 SpringDoc 的能力范围已经不只是传统 MVC Controller,而覆盖:

1
2
3
4
Spring MVC Annotation
Spring MVC Functional
Spring WebFlux Annotation
Spring WebFlux Functional

构建阶段生成 OpenAPI

OpenAPI 不一定只能在应用运行以后访问:

1
/v3/api-docs

SpringDoc 还提供 Maven/Gradle Plugin,可以在构建流程中生成 JSON/YAML 文档。

SpringDoc Maven Plugin 可以和 Spring Boot Maven Plugin 配合,在 integration-test 生命周期中生成 OpenAPI;官方示例可以通过:

1
mvn verify

触发。

这样 CI 可以变成:

flowchart LR
    A["git push"]
    B["Maven Build"]
    C["启动测试应用"]
    D["生成 openapi.json"]
    E["API Contract Check"]
    F["生成 SDK"]
    G["发布制品"]

    A --> B
    B --> C
    C --> D
    D --> E
    E --> F
    F --> G

这时 OpenAPI 就开始从“方便前端调接口的页面”,升级为软件工程中的正式制品。

例如可以用于:

  • API Breaking Change 检查;
  • 自动生成 Java/TypeScript SDK;
  • Mock Server;
  • Contract Test;
  • API Gateway 导入;
  • API Portal;
  • 自动化测试。

这比单纯“项目里装个 Swagger”更接近 OpenAPI 的真正价值。

从 Springfox 迁移

假设旧项目原来使用:

1
2
3
4
5
6
7
8
9
10
@Api(tags = "Book")
public class BookController {

@ApiOperation(
value = "查询图书",
notes = "根据 ID 查询图书"
)
public Book find(...) {
}
}

迁移后:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Tag(
name = "Book",
description = "图书管理"
)
public class BookController {

@Operation(
summary = "查询图书",
description = "根据 ID 查询图书"
)
public Book find(...) {
}
}

模型:

1
2
3
4
5
6
@ApiModel("Book")
public class Book {

@ApiModelProperty("图书 ID")
private Long id;
}

变成:

1
2
3
4
5
6
@Schema(description = "图书")
public class Book {

@Schema(description = "图书 ID")
private Long id;
}

如果原来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Bean
public Docket publicApi() {
return new Docket(
DocumentationType.SWAGGER_2
)
.select()
.paths(
PathSelectors.regex(
"/public.*"
)
)
.build()
.groupName("public");
}

迁移为:

1
2
3
4
5
6
7
@Bean
public GroupedOpenApi publicApi() {
return GroupedOpenApi.builder()
.group("public")
.pathsToMatch("/public/**")
.build();
}

SpringDoc 官方迁移指南已经给出了这一套对应关系。

迁移时最好不要采取:

1
Springfox + SpringDoc 同时存在

再慢慢换注解。

更合理的是:

1
2
3
4
5
6
7
8
9
10
11
删除 Springfox

换 SpringDoc Starter

迁移 Swagger 2 注解

迁移 Docket

验证 /v3/api-docs

验证 Swagger UI

这样依赖关系更干净,也更容易定位问题。

常见问题与排查

/swagger-ui.html 打不开

先不要急着怀疑 Swagger UI。

按照下面的顺序排查:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
1. /v3/api-docs 是否正常?

├─ 否 → SpringDoc 本身有问题

└─ 是


2. /swagger-ui.html 是否返回?


3. Spring Security 是否拦截?


4. context-path 是否发生变化?


5. 反向代理是否正确转发?

首先访问:

1
http://localhost:8080/v3/api-docs

如果这个地址都不存在,问题一般不在 Swagger UI 页面。

Spring Security 返回 401 / 403

检查:

1
2
3
4
5
6
.requestMatchers(
"/v3/api-docs/**",
"/swagger-ui/**",
"/swagger-ui.html"
)
.permitAll()

如果生产环境本来就不应该开放,则不要为了“修好 Swagger”盲目放开权限。

Controller 没有出现在文档中

如果使用的是:

1
@Controller

但没有:

1
@ResponseBody

SpringDoc 默认可能不会把它识别为 REST Controller。

推荐使用:

1
@RestController

SpringDoc 官方 FAQ 对这一行为有明确说明。

DTO 参数没有展开

对于 Query Object:

1
2
3
public Page<BookDTO> list(
BookQuery query
)

可以尝试:

1
2
3
public Page<BookDTO> list(
@ParameterObject BookQuery query
)

并确保对象有标准 Getter。

Spring Boot 3.2 之后参数名缺失

Spring Boot 3.2 的 Parameter Name Discovery 变化可能导致生成的 OpenAPI 中部分参数信息缺失。

SpringDoc 官方建议让 Java 编译器保留方法参数名称:

1
2
3
4
5
6
7
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<parameters>true</parameters>
</configuration>
</plugin>

也就是启用 Java:

1
-parameters

如果遇到:

1
2
arg0
arg1

或者 Path/Query Parameter 名称无法正常识别,这一项值得优先检查。

自定义 HttpMessageConverter 后 Swagger UI 无法解析文档

如果项目自己完全覆盖了 Spring Boot 默认 HttpMessageConverter,可能破坏 SpringDoc 文档响应。

SpringDoc 官方特别指出,应保留:

1
ByteArrayHttpMessageConverter

例如:

1
2
3
4
5
6
7
converters.add(
new ByteArrayHttpMessageConverter()
);

converters.add(
new MappingJackson2HttpMessageConverter(...)
);

而且 converter 顺序也会影响行为。

所以遇到:

1
Swagger UI unable to render definition

不要只检查 Swagger 注解。

如果项目改过:

1
configureMessageConverters(...)

这个地方应该重点检查。

Swagger UI 能开,但没有任何 API

检查:

1
2
3
4
springdoc:
packages-to-scan:
paths-to-match:
paths-to-exclude:

例如:

1
2
3
springdoc:
packages-to-scan:
- com.example.foo.controller

但 Controller 实际在:

1
com.example.bar.controller

SpringDoc 自然扫描不到。

Swagger UI 页面存在,但 Fetch /v3/api-docs 失败

检查是不是写了:

1
2
3
4
5
6
springdoc:
api-docs:
enabled: false

swagger-ui:
enabled: true

Swagger UI 的数据源就是 OpenAPI JSON。

没有 /v3/api-docs,UI 就只剩一个壳。

JWT 无法在 Swagger UI 中发送

不要写:

1
2
3
@Parameter(
name = "Authorization"
)

OpenAPI 规范专门使用 Security Scheme 表达 Authorization,SpringDoc 官方也推荐 @SecurityRequirement + Bearer Security Scheme。

正确模型应该是:

1
2
3
4
5
SecurityScheme

└── HTTP Bearer

└── JWT

而不是普通 Header Parameter。

版本升级后突然报错

优先检查三件事:

1
2
3
Spring Boot Version
SpringDoc Version
Swagger Core Version

不要看到:

1
2
3
NoSuchMethodError
ClassNotFoundException
NoClassDefFoundError

就先开始修改 Controller。

这种错误非常可能来自版本不兼容。

SpringDoc 的缓存机制

SpringDoc 默认会计算 OpenAPI Definition 并缓存结果。

官方属性:

1
2
3
springdoc:
cache:
disabled: false

如果设置:

1
2
3
springdoc:
cache:
disabled: true

则可以关闭 OpenAPI Cache。官方说明,默认情况下 OpenAPI 描述会计算一次并缓存;在某些内部/外部代理场景中,可能需要每次请求重新计算 Server URL。

正常项目不要为了“保证最新”随便关缓存。

Controller 和 DTO 本身不会在应用运行过程中不断发生变化,关闭缓存只会增加没有必要的文档生成开销。

开发期热更新场景另当别论。

接口文档应该写到什么程度

有了 Swagger 注解之后,另一个常见问题是:注解写得太多。

例如:

1
2
3
4
5
6
7
@Operation(
summary = "getUser",
description = "getUser"
)
@GetMapping("/users/{id}")
public User getUser(...) {
}

这是无效信息。

又例如:

1
2
3
4
@Schema(
description = "String 类型的用户名"
)
private String username;

重点也不应该是告诉读者:

1
username 是 String

因为 Schema 本来就能推断。

真正有价值的是:

1
2
3
4
5
@Schema(
description = "用户登录名,全租户范围唯一",
example = "mario"
)
private String username;

接口文档应该补充的是代码无法直接表达的语义

可以用一个简单原则判断:

删除这段 Swagger 注解之后,消费者是否会失去重要业务信息?

如果不会,那这段注解大概率没有必要。

推荐的注解职责划分

一个比较合理的工程规范可以是:

层级 推荐内容
Controller @Tag
API Method @Operation
Path/Query/Header 必要时使用 @Parameter
DTO / VO @Schema
Validation Bean Validation
HTTP Response @ApiResponse
Authentication @SecurityScheme / @SecurityRequirement
API Group GroupedOpenApi
全局元数据 OpenAPI Bean

避免:

1
Controller 上塞几十行 OpenAPI 配置

也避免:

1
每个字段都写一堆显而易见的注解

一个更适合实际项目的配置结构

对于中大型 Spring Boot 项目,可以把 OpenAPI 配置集中在:

1
2
3
4
5
config
└── openapi
├── OpenApiConfig.java
├── OpenApiGroupConfig.java
└── OpenApiSecurityConfig.java

例如:

1
2
3
4
5
6
7
8
9
10
11
12
OpenApiConfig
└── title
└── version
└── description

OpenApiGroupConfig
└── public
└── admin
└── internal

OpenApiSecurityConfig
└── bearerAuth

业务代码:

1
2
3
4
controller
├── UserController
├── RoleController
└── PermissionController

只关心:

1
2
3
4
@Tag
@Operation
@Parameter
@ApiResponse

数据模型:

1
2
3
4
dto
├── CreateUserRequest
├── UpdateUserRequest
└── UserDTO

只负责:

1
2
@Schema
Bean Validation

这样文档配置不会反过来污染业务代码结构。

API 文档不应该成为第二套类型系统

一些项目为了 Swagger 会出现大量这种代码:

1
2
@Schema(type = "string")
private String name;
1
2
@Schema(type = "integer", format = "int64")
private Long id;

SpringDoc 本身已经能够从 Java 类型推断这些内容。

如果你的 Java 字段已经是:

1
private Long id;

就没有必要再重复告诉 SpringDoc:

1
这是一个 int64

更加值得描述的是:

1
2
3
4
5
@Schema(
description = "订单 ID,由服务端生成",
example = "190000000000000001"
)
private Long id;

接口文档应该增强代码语义,而不是复制代码事实。

OpenAPI 可以成为服务契约

如果只是把 SpringDoc 理解为:

“给前端看接口的 Swagger 页面。”

会低估 OpenAPI 的价值。

一旦系统建立:

1
2
3
4
5
Controller

SpringDoc

openapi.json

这份文件就可以继续进入工程链路:

flowchart TD
    A["Spring Boot API"]
    B["SpringDoc"]
    C["openapi.json"]

    D["Swagger UI"]
    E["API Portal"]
    F["SDK Generator"]
    G["Contract Test"]
    H["Breaking Change Check"]
    I["API Gateway"]
    J["Mock Server"]

    A --> B
    B --> C

    C --> D
    C --> E
    C --> F
    C --> G
    C --> H
    C --> I
    C --> J

这才是成熟项目中更值得建立的目标:

1
2
3
4
5
代码

API Contract

自动化工具链

而不是:

1
2
3
代码

一个好看的网页

2026 年之后的 SpringDoc 演进

SpringDoc 现在已经不再只有传统 Swagger UI。

当前官方文档同时提供:

  • Swagger UI;
  • Scalar;
  • Spring MVC;
  • WebFlux;
  • Spring Security;
  • Actuator;
  • GraalVM Native Image;
  • OpenAPI 3;
  • Spring Boot 4;
  • MCP 集成。

SpringDoc 3.x 已经进入 Spring Boot 4 时代,当前官方首页使用 3.x starter,并提供面向 Spring Boot 4 的文档;Spring Boot 3 则继续保留独立的 v2 文档。

SpringDoc 3.x 当前还增加了 MCP 模块,可以把已有 REST API 基于 OpenAPI 映射为 AI Tool。

这说明 OpenAPI 在今天的价值已经不仅仅是:

1
后端 ↔ 前端

它正在逐渐成为:

1
2
3
4
5
Service

Machine Readable Contract

Frontend / SDK / Gateway / Test / AI Agent

因此从长期工程视角看,比“选择 Swagger UI 还是 Scalar”更重要的问题是:

你的 API 是否拥有准确、稳定、机器可读、可以持续演进的契约。

推荐的工程实践

综合 SpringDoc 的能力和实际项目维护成本,可以采用下面这套原则:

  1. Spring Boot 3 使用 SpringDoc 2.x,Spring Boot 4 使用 SpringDoc 3.x,不要跨主版本随意组合。当前版本变化较快,应优先检查官方发布说明和实际 Boot 版本。
  2. Spring MVC 使用 springdoc-openapi-starter-webmvc-*,WebFlux 使用 springdoc-openapi-starter-webflux-*
  3. Controller 负责 @Tag@Operation 等接口语义,不要用 OpenAPI 注解重复 Spring MVC 已经能够表达的信息。
  4. DTO 使用 @Schema 描述业务语义,数据约束仍然交给 Bean Validation。
  5. 使用 OpenAPI Bean 管理全局元数据和 Security Scheme。
  6. JWT、OAuth2 等认证使用 OpenAPI Security Scheme,不要把 Authorization 当普通 Header 参数处理。
  7. 大型系统使用 GroupedOpenApi 按业务域拆分 API。
  8. Spring Security 环境下明确规划 Swagger/OpenAPI endpoint 的访问策略。
  9. 开发环境可以开放 Swagger UI,生产环境根据安全要求关闭、限制内网访问或迁移至 Management Port。
  10. 微服务更应该维护 /v3/api-docs 这份 API Contract,而不是依赖每个服务各自的 Swagger 页面。
  11. 将 OpenAPI JSON/YAML 纳入 CI,可以进一步实现 Breaking Change 检查、SDK 生成、Mock、Contract Test 等能力。
  12. 升级 Spring Boot 时同时检查 SpringDoc 兼容关系,不要把接口文档组件当作完全独立的普通工具依赖。

总结

SpringDoc 的真正价值,不是给 Spring Boot 项目增加一个 Swagger 页面,而是把真实的 Spring Web 接口结构转换为标准 OpenAPI Contract。

整个体系可以概括为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Spring MVC / WebFlux


Controller + DTO + Validation


Swagger / OpenAPI Annotations


SpringDoc


OpenAPI 3

├── JSON
└── YAML

├── Swagger UI
├── Scalar
├── API Gateway
├── SDK Generator
├── Contract Test
└── API Portal

对于 Spring Boot 3 项目,当前应以 SpringDoc 2.x starter 体系为基础;进入 Spring Boot 4 后,则切换到 SpringDoc 3.x。SpringDoc 可以从 Controller、请求参数、DTO、Validation 和 OpenAPI 注解中生成绝大部分接口结构,开发者真正需要维护的应该是代码无法自动推断出的业务语义。

当接口数量较少时,它只是减少手工写文档的工作;当系统进入模块化单体、微服务、API Gateway、SDK 自动生成和契约测试阶段后,OpenAPI 则可以逐渐成为整个服务体系中的正式接口契约。到了这个阶段,Swagger UI 只是其中一个查看入口,而不再是 API 文档本身。


Spring Boot 使用 SpringDoc 构建 OpenAPI 3 接口文档
https://allendericdalexander.github.io/2026/08/16/java/spring/Spring-Boot-SpringDoc-OpenAPI-3/
作者
AtLuoFu
发布于
2026年8月16日
许可协议