接口文档有两种失败方式:一种干脆没有,另一种是生成了 Swagger UI 但没人愿意打开——一个页面堆几百个接口,展开靠手点,搜索靠 Ctrl+F,请求示例要自己拼 JSON。Knife4j 治的是后一种:它不生成 OpenAPI 描述,只把 springdoc 产出的那份 JSON 换一套前端来渲染,再补上调试面板、全局参数、离线导出和分组树。
一句话定位:springdoc 决定"文档是什么",Knife4j 决定"文档怎么看"。认清这条分界,后面每个配置项才有地方安放。
1. 它到底是什么:一层 UI,不是另一套扫描器
| 层 | 组件 | 负责 | 不该指望它 |
|---|---|---|---|
| 描述生成 | springdoc-openapi 2.x |
扫注解与 Controller 签名,输出 /v3/api-docs JSON |
好看的界面 |
| 渲染前端 | Knife4j 的 doc.html + webjars |
分组树、在线调试、全局参数、离线文档 | 生成描述 |
| 增强后端 | knife4j-openapi3-jakarta-spring-boot-starter |
挂 UI 资源、提供鉴权与屏蔽开关 | 替代 springdoc |
所以 /doc.html 打开是白屏时,九成不是 Knife4j 坏了,是它拿不到 JSON。先直接访问 /v3/api-docs,有 JSON 才能谈界面。
2. 依赖选择:Boot 3 只认 jakarta 那一条
// Spring Boot 3.3.x / JDK 17 的 Maven 坐标
// <dependency>
// <groupId>com.github.xiaoymin</groupId>
// <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>
// <version>4.5.0</version>
// </dependency>
| 场景 | 坐标 | 说明 |
|---|---|---|
| Boot 3.x / JDK 17 | com.github.xiaoymin:knife4j-openapi3-jakarta-spring-boot-starter:4.5.0 |
jakarta.* 命名空间,随包带入 springdoc 2.x |
| 只要 UI,不要后端增强 | com.github.xiaoymin:knife4j-openapi3-ui |
自己另配 springdoc 版本 |
| Boot 2.x | com.github.xiaoymin:knife4j-openapi3-spring-boot-starter |
非 jakarta,和上一条不能混 |
| Boot 2.x + swagger2 | com.github.xiaoymin:knife4j-spring-boot-starter:3.x |
老坐标,停在维护状态 |
| 只要原生界面 | org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0 |
没有 Knife4j 的增强面板 |
最容易踩的一条:Boot 3 工程里填了 knife4j-spring-boot-starter。启动不报错,/doc.html 却 404 或空白——那条依赖链走的是 springfox + javax.servlet,jakarta 环境下扫不到任何 Controller,JSON 是空的。坐标对不上版本,后面全白干。
如果 4.5.0 带入的 springdoc 与 Boot 3.3 的 ControllerAdviceBean 之类内部 API 打架(典型报 NoSuchMethodError),显式把 springdoc-openapi-starter-webmvc-ui 钉到 2.6.0 覆盖传递版本,具体以官方 4.5.0 依赖树核对。
3. 最小配置:五行 yaml 起站
springdoc:
api-docs:
enabled: true
path: /v3/api-docs
packages-to-scan: com.example.clarity.web
paths-to-match: /api/**
knife4j:
enable: true
setting:
language: zh_cn
| 键 | 作用 | 不配的后果 |
|---|---|---|
springdoc.api-docs.path |
描述 JSON 路径 | 默认 /v3/api-docs,改了要同步改网关聚合地址 |
springdoc.packages-to-scan |
限定扫描包 | 全量扫描,启动慢且把内部接口一起暴露 |
springdoc.paths-to-match |
按 URL 过滤 | 写的是 Controller 映射,不含 context-path |
knife4j.enable |
是否挂 UI | false 时 /doc.html 直接 404 |
knife4j.setting.language |
界面语言 | 默认英文,zh_cn 才有中文文案 |
访问地址是 http://localhost:8080/doc.html。若配了 server.servlet.context-path=/clarity,实际是 /clarity/doc.html——大量"404"就死在这里,Spring Boot 3 的 context-path 会作用到静态资源。
4. 注解:三个注解各管一段
@Tag(name = "文章管理", description = "博客文章的增删改查")
@RestController
@RequestMapping("/api/articles")
public class ArticleController {
@Operation(summary = "分页查询文章", description = "按发布时间倒序,可按标签过滤")
@GetMapping
public PageResult<ArticleVO> page(
@Parameter(description = "页码,从 1 开始") @RequestParam(defaultValue = "1") int page,
@Parameter(description = "每页条数") @RequestParam(defaultValue = "20") int size) {
return service.page(page, size);
}
}
| 注解 | 写在哪 | 常见误用 |
|---|---|---|
@Tag |
Controller 类 | 写成 swagger2 的 @Api,Boot 3 下完全无效 |
@Operation |
方法 | summary 写一句话,细节塞 description |
@Parameter |
方法参数 | 基本类型不写就显示成 param1 |
@Schema |
DTO 类/字段 | 字段不写,界面没有中文说明和示例值 |
@Hidden |
类/方法/字段 | 内部接口用它,比配 paths-to-exclude 精确 |
@Schema(description = "文章创建入参")
public record ArticleCreateDTO(
@Schema(description = "标题", example = "分片路由的四步",
requiredMode = Schema.RequiredMode.REQUIRED) @NotBlank String title,
@Schema(description = "正文 Markdown", example = "# 标题\n正文") @NotBlank String content,
@Schema(description = "标签", example = "[\"java\",\"db\"]") List<String> tags,
@Schema(hidden = true) String operator) {}
record 在 springdoc 2.x 下能正常解析构造参数上的 @Schema,前提是注解写在参数上而不是 record 组件外。
5. 分组与聚合:两套机制别混用
单体应用内分组用 group-configs,每个分组是一个独立的 JSON 端点:
springdoc:
group-configs:
- group: blog-web
packages-to-scan: com.example.clarity.web
paths-to-match: /api/**
- group: blog-admin
packages-to-scan: com.example.clarity.admin
paths-to-match: /admin/**
网关聚合是另一回事。knife4j-gateway-spring-boot-starter 属于 Spring Cloud Gateway 生态,从注册中心拉服务清单自动拼;而在单体或多服务手工清单场景,用 Spring 原生的静态地址列表更稳:
springdoc:
swagger-ui:
urls:
- name: blog
url: /blog/v3/api-docs
- name: quiz
url: /quiz/v3/api-docs
区别是:静态清单需要你保证每个 url 可达且 CORS 放行;自动聚合需要网关能发现实例,实例下线后菜单会残留。
6. 生产环境怎么关
| 做法 | 效果 | 代价 |
|---|---|---|
knife4j.production=true |
文档资源被 Knife4j 自己的过滤器拦下 | 依赖仍在,类加载开销照付 |
springdoc.api-docs.enabled=false |
根本不生成 JSON | /doc.html 打开是空壳 |
| Maven 按 profile 不打包 | 生产 jar 里没有 UI 资源 | 构建矩阵变复杂 |
| nginx / 网关 deny | 外部访问不到 | 内网仍可直达 |
按官方 4.5.0 文档,knife4j.production=true 后文档资源不可访问;但它拦的是 Knife4j 的资源路径,/v3/api-docs 是否仍直接暴露取决于 springdoc 版本。稳妥组合是两个都关:
# application-prod.yaml
springdoc:
api-docs:
enabled: false
knife4j:
enable: false
production: true
# application-dev.yaml
springdoc:
api-docs:
enabled: true
knife4j:
enable: true
production: false
basic:
enable: true
username: doc
password: CHANGE_ME
knife4j.basic 的密码明文躺在配置文件里,只能当内网挡板,不能当认证。
7. 鉴权体系下的文档访问
Spring Security 默认拦一切时,文档页必须显式放行这几类资源:
| 路径 | 用途 | 漏放行的表现 |
|---|---|---|
/doc.html |
UI 入口 | 302 跳登录页或 401 |
/webjars/** |
JS / CSS | 白屏、样式全丢 |
/v3/api-docs/** |
描述 JSON | 左侧分组树空 |
/swagger-resources/** |
分组元数据 | 分组下拉只有一项或为空 |
@Bean
SecurityFilterChain docChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(auth -> auth
.requestMatchers("/doc.html", "/webjars/**", "/v3/api-docs/**",
"/swagger-resources/**", "/favicon.ico").permitAll()
.anyRequest().authenticated());
return http.build();
}
接口本身需要登录时,调试面板里的请求会 401——这不是 Knife4j 的问题,是 UI 不带你的 Cookie/Token。解决方式是全局参数:
knife4j:
setting:
enable-swagger-models: true
然后在界面的"全局参数设置"里加一个 Authorization header,它会附加到该分组的所有调试请求上。
8. 排错:六种打不开
| 现象 | 原因 | 处理 |
|---|---|---|
/doc.html 404 |
依赖是 springfox 老坐标 / knife4j.enable=false / context-path 没带 |
换 jakarta 坐标,检查 server.servlet.context-path |
| 页面打开但白屏 | /webjars/** 或 /v3/api-docs 被拦 |
放行静态资源,F12 看哪个请求 401/404 |
| 分组树是空的 | 扫描包没覆盖,或 @EnableWebMvc 破坏了自动配置 |
去掉 @EnableWebMvc,放宽 packages-to-scan |
| JSON 里接口重复两遍 | 同一个类被两个 group-configs 匹配 |
用不重叠的 paths-to-match |
启动报 NoSuchMethodError |
springdoc 版本与 Boot 3.3 不匹配 | 显式钉 springdoc 版本 |
| 上传接口调试报 415 | 文档里没声明 multipart/form-data |
用 @RequestBody 配 @Schema 或改用 @RequestPart |
@EnableWebMvc 这条特别隐蔽:它会让 Spring Boot 的 WebMvc 自动配置整体退让,springdoc 的资源注册器随之失效,表现是 /v3/api-docs 404 而不是报错。
9. 边界:什么时候别上 Knife4j
| 场景 | 更合适的做法 | 理由 |
|---|---|---|
| 纯前后端交付、需要契约先行 | OpenAPI 文件 + codegen | 手写注解生成的契约不可评审 |
| 对外公开 API | 独立文档站(Redoc / Stoplight) | UI 和线上鉴权解耦 |
| 服务超过十个 | 网关聚合 + 统一门户 | 每个服务各挂一份 UI 没法用 |
| 只需要偶尔看一次 | /v3/api-docs + 任意 JSON 查看器 |
不值得为界面加一个依赖 |
| 生产必须零额外攻击面 | 构建期生成静态 HTML | 运行期不留任何文档端点 |
10. 自测
/doc.html能打开但分组树为空,第一步该直接访问哪个地址?- Boot 3 工程里用了
knife4j-spring-boot-starter,为什么启动不报错却扫不到接口? springdoc.paths-to-match里要不要带server.servlet.context-path?knife4j.production=true和springdoc.api-docs.enabled=false各关掉了什么?- 安全配置里除了
/doc.html,还有哪三类路径必须放行? - 什么时候该用
group-configs,什么时候该用springdoc.swagger-ui.urls?