springdoc-openapi-demos:OpenAPI 3 接口文档参考实现

发布时间:2026/10/26 13:30:43
springdoc-openapi-demos:OpenAPI 3 接口文档参考实现 简介面向Java与Spring Boot开发者的springdoc-openapi-demos示例合集聚焦OpenAPI 3规范在微服务接口文档中的落地适合希望掌握接口描述、文档生成与调试协作的中级开发者参考。压缩包共219个文件约281KB以139个Java源码、23个XML配置、21个YML配置为主并含Markdown说明和少量属性、JSON等辅助文件覆盖依赖接入、分组配置与示例代码组织。已有967人学习下载说明其在同类示例中具备一定参考价值。项目通过多个演示呈现全局信息定义、接口与参数标注、请求响应描述、API分组以及安全认证等实践并配合Swagger UI完成接口验证与调试读者可据此理解注解用法、目录模块划分和配置思路快速对照搭建自己的OpenAPI 3文档体系提升接口维护效率。1. springdoc-openapi-demos一份能直接跑起来的 OpenAPI 3 参考实现接手一个跑了三四年的 Spring Boot 服务接口散在十几个 Controller 里前端第三次来问「这个字段到底传不传、传空字符串行不行」这种场景下手写的接口文档活不过两个迭代。springdoc-openapi-demos 是给 Java 后端准备的一组可运行参考实现把 springdoc-openapi 挂在 Spring Boot 上启动时扫 Controller运行时吐出一份符合 OpenAPI 3 规范的 JSON再配一个 Swagger UI 页面直接发请求验证。它适合三类人正把老项目从 springfox 往 OpenAPI 3 迁移的团队、想给前后端联调找一份「跟着代码走」的契约的开发者、以及想看清 Spring WebMVC 和 WebFlux 两种栈下文档生成差异的人。它的价值不在于多一个库而在于把「文档和代码怎么不脱节」这件事拆成了可直接对照的模块。2. 仓库模块地图WebMVC、WebFlux、Kotlin 三套 demo 怎么挑拿到一个 demo 仓库先别急着 clone 完就 mvn spring-boot:run。这个仓库的组织方式是按技术栈切模块的每个模块都是一个能独立启动的小服务对应一种运行时。挑错模块后面所有注解、配置、排错经验都会对不上号。2.1 模块与技术栈的对应关系springdoc 有两套发布线命名不一样混用是最常见的第一个翻车点。1.x 线对应 Spring Boot 2.xartifactId 不带 starter 前缀2.x 线对应 Spring Boot 3.x走 starter 命名。仓库里的 demo 也是按这两条线分开的看模块名基本能判断它对应哪条线。模块/依赖适用栈什么时候拿它当模板需要留意的点starter-webmvc-uiServlet 阻塞式绝大多数传统 REST 服务自带 Swagger UI 静态资源starter-webmvc-apiServlet 阻塞式只想产出 openapi.jsonUI 另外托管不含 UI 页面starter-webflux-ui响应式WebFlux网关类、流式接口函数式端点默认扫不到Kotlin 变体Kotlin 协程想保留可空类型语义可空性会映射成 requiredmaven-plugin构建期CI 里导出 openapi.json需要应用在构建阶段启动选模块的判断标准其实只有一条你的服务用的是spring-boot-starter-web还是spring-boot-starter-webflux。前者选 webmvc后者选 webflux别为了「看起来更现代」在阻塞栈上硬上 WebFlux 那套文档生成没问题但运行时的线程模型会给你带来完全无关的麻烦。2.2 为什么是运行时扫描而不是手写 YAMLspringdoc 的工作方式是运行时反射加容器内省应用启动完成后它遍历RequestMappingHandlerMapping里已经注册好的 handler把RequestMapping的路径、方法、参数元数据和Operation、Parameter这类注解合并成 OpenAPI 的 Operation 对象最后由 Jackson 序列化成 JSON。这也解释了两件事为什么加一个 starter 就能出文档以及为什么 Controller 上标注写错了文档一定跟着错。和几种常见做法对比选型理由更清楚方案与代码的一致性额外构建步骤WebFlux 支持维护成本运行时扫描springdoc高跟着注解走无原生低springfox中且对 OpenAPI 3 支持不完整无弱迁移成本高手写 YAML低靠人肉同步有无关越久越贵编译期注解处理器高有看实现中手写 YAML 的问题不是麻烦是「漂移」——代码改了没人改 YAML三个月后这份文件就是负资产。运行时扫描把文档生成挂在应用启动链路上Controller 变了文档必然重算这是它最实在的优势。代价是文档只能在应用能启动的前提下生成单测里想拿文档就得起容器这一点后面第 6 章会处理。2.3 从零跑通第一个 demo以 Spring Boot 3 线为例依赖只加一个。版本号用属性占位别在多个模块里写死升级时你会感谢自己。!-- pom.xmlBoot 3 线用 starter 命名Boot 2 线把 artifactId 换成 springdoc-openapi-ui -- dependency groupIdorg.springdoc/groupId artifactIdspringdoc-openapi-starter-webmvc-ui/artifactId version${springdoc.version}/version /dependency配置文件里把扫描范围和路径过滤先定下来这两项决定了文档里会不会混进 actuator 之类的内部端点。springdoc: api-docs: path: /v3/api-docs # JSON 契约的默认地址别随便改前端工具链默认认这个 enabled: true swagger-ui: path: /swagger-ui.html # UI 入口最终会 302 到 /swagger-ui/index.html operations-sorter: method # 按 GET/POST 排序比按字母序更符合阅读习惯 tags-sorter: alpha disable-swagger-default-url: true # 防止 UI 去拉公网上的宠物商店示例 packages-to-scan: com.example.demo.web # 只扫业务包减少无关 schema paths-to-match: /api/** # 与上一条同时生效取交集启动之后先别开浏览器用 curl 验证契约本身是否生成成功# 只取三个关键字段确认不是空壳 curl -s http://localhost:8080/v3/api-docs \ | jq {openapi: .openapi, title: .info.title, pathCount: (.paths | keys | length)}这段输出里openapi字段应该是 3.0.x 或 3.1.xpathCount大于 0 才算真的扫到了接口。如果返回 404先确认没有配server.servlet.context-path——配了的话真实地址会变成/你的前缀/v3/api-docs这是新手最常卡住的地方。如果返回 200 但paths是空的八成是packages-to-scan写错或者paths-to-match把接口全过滤掉了把这两个配置临时注释掉再试一次能快速定位是过滤问题还是扫描问题。提示packages-to-scan和paths-to-match不是二选一两个都配时按交集算。想放宽就先把paths-to-match去掉。3. 注解落地把 Controller 变成一份能读的 OpenAPI 3 文档生成的文档「有」和「能用」是两回事。默认扫描出来的 Operationsummary 是方法名参数描述是空的响应体是一个笼统的 Object。真正要交付给前端得把注解补到该补的地方。3.1 Operation、Parameter、Schema 的职责边界这三个注解经常被混着用结果就是同一个说明写了两遍还互相打架。分工其实很清晰注解作用对象写什么常见误用Operation方法接口级摘要、operationId、是否废弃把字段说明写进来Parameter方法参数单个入参的描述、示例、是否必填用它描述请求体字段Schema类 / 字段 / 类型数据模型的字段约束、示例、取值范围用它改接口摘要operationId值得单独说一句它默认是方法名如果你的 Controller 里有重载或者多个类里有同名方法生成的 SDK 会出现方法冲突。养成显式写operationId的习惯尤其是要给前端或外部团队生成客户端代码的时候。RestController RequestMapping(/api/v1/users) Tag(name 用户, description 用户查询与创建) public class UserController { Operation(summary 按 ID 查询用户, operationId getUserById) GetMapping(/{id}) public ApiResultUserVO get( // in 不写也能推断但显式声明在跨栈迁移时更稳 Parameter(description 用户 ID, example 1001, required true) PathVariable Long id) { return ApiResult.ok(userService.find(id)); } }方法上的注解解决「这个接口干什么」字段上的Schema解决「这个字段填什么」。比如金额字段用Schema(description 订单金额单位分, example 1999, minimum 0)比在接口摘要里写一段话有用得多因为 Swagger UI 会把它渲染到字段旁边前端不用来回翻。注意Parameter的required默认值和 Spring 自己的RequestParam(required ...)是两套东西。两边不一致时以 springdoc 的推断为准的情况更多最稳的做法是显式写清楚别依赖默认值。3.2 统一响应体与泛型的 Schema 建模国内项目几乎都有ApiResultT这层包装。它在运行时没问题但在文档层面会遇到类型擦除ApiResultUserVO和ApiResultOrderVO如果不做处理可能被合并成同一个 schema前端看到的data字段就变成了一个没有字段的空对象。springdoc 对泛型是有解析能力的会按实际类型参数生成形如ApiResultUserVO的独立 schema。麻烦在于命名规则不稳定2.x 线里全限定名开关的默认值和 1.x 不一样升级之后 schema 名突然带上包名前端生成的类型文件跟着全变这是升级时最容易踩的坑之一。// 统一响应体用 Schema 把名字钉死避免升级后 schema 名漂移 Schema(name ApiResultUser, description 用户查询统一响应) public class ApiResultUser { Schema(description 业务码0 表示成功, example 0) private int code; Schema(description 提示信息, example ok) private String message; Schema(description 业务数据) private UserVO data; }如果不想为每个泛型组合都写一个子类另一条路是在配置里把泛型覆盖关掉让 springdoc 老老实实按声明的原始类型生成springdoc: # 关掉对 ResponseEntity / 泛型返回值的自动覆盖改为按方法签名原始类型建模 override-with-generic-response: false这两条路的取舍钉死名字可读性好、前端类型稳定代价是每加一种泛型组合就要多一个类关掉覆盖省事但前端看到的data是弱类型约束全靠文档描述兜。团队小、接口少选前者接口几十上百个选后者再配合Schema在 VO 上做约束。3.3 GroupedOpenApi按版本和端拆分文档一个服务里同时存在/api/v1/**和/api/v2/**或者对内接口和开放接口混在一起全塞进一份文档前端打开就是几百个接口糊在页面上。GroupedOpenApi解决的就是这个每个分组会生成独立的 JSON 地址/v3/api-docs/{group}UI 右上角也会出现下拉框。Configuration public class OpenApiGroupConfig { Bean public GroupedOpenApi v1Group() { return GroupedOpenApi.builder() .group(v1) // 决定访问路径 /v3/api-docs/v1 .pathsToMatch(/api/v1/**) .packagesToScan(com.example.demo.web.v1) // 与上一条取交集 .build(); } Bean public GroupedOpenApi openGroup() { return GroupedOpenApi.builder() .group(open) .pathsToMatch(/open/**) .addOpenApiCustomizer(openApi - openApi.info(new Info().title(开放接口).version(1.0))) .build(); } }addOpenApiCustomizer是这个 bean 里最值得用的扩展点它在文档组装完成、序列化之前被调用可以按分组改标题、加安全方案、删掉不想暴露的 tag。比起全局改OpenAPIbean按分组定制不会污染其他组的输出。分组还有一个隐性收益它把「文档范围」变成了一个可以写进代码评审的东西。加了packagesToScan之后谁往这个包里塞了 Controller文档里就会多出来评审时能看见。4. 安全方案与 UI 定制JWT bearer 和 Swagger UI 的那些开关文档能看之后下一个问题是「能不能在页面上直接调」。带鉴权的服务不配安全方案Swagger UI 上的 Try it out 永远是 401前端只能复制 curl 去 Postman 里改文档的价值折掉一半。4.1 SecurityScheme把 JWT 声明进 OpenAPI声明分两步先在components里定义方案再用SecurityRequirement声明哪些接口需要它。全局加一次所有接口默认带锁图标个别公开接口用SecurityRequirement(name )或者在 customizer 里按路径剔除。Bean public OpenAPI openAPI() { return new OpenAPI() .info(new Info().title(示例服务).version(v1).description(内部接口契约)) .components(new Components().addSecuritySchemes(bearerAuth, new SecurityScheme() .type(SecurityScheme.Type.HTTP) .scheme(bearer) .bearerFormat(JWT) .in(SecurityScheme.In.HEADER) .name(Authorization))) // 全局生效不想全局就把这行去掉改用方法级注解 .addSecurityItem(new SecurityRequirement().addList(bearerAuth)); }type选HTTP加schemebearer是 OpenAPI 3 里描述 JWT 的标准写法UI 会渲染成一个「Authorize」按钮填一次 token 全站生效。别用apiKey去模拟Authorization头虽然也能跑但生成的客户端代码不会自动带上Bearer前缀前端接进去会多花半天排查。persist-authorization: true这个开关建议打开刷新页面不用重新填 token。反过来如果文档要挂到内网共享环境记得关掉不然上一个人的 token 会留在浏览器里。4.2 Swagger UI 的几个实用开关UI 的配置项很多真正影响日常使用的不超过十个配置项作用建议值swagger-ui.pathUI 入口路径改成 /docs避免和业务路径撞operations-sorter接口排序methodtags-sorter分组排序alphafilter顶部搜索框true接口多时必开default-models-expand-depth模型区展开层级-1 全收起-2 全展开persist-authorization记住 token内网 true对外 falsetry-it-out-enabled是否允许页面直接调生产环境务必 falsedefault-models-expand-depth这个参数名字很反直觉负数表示折叠-1 是全部收起-2 是全部展开。第一次配的时候我按正数填页面纹丝不动查了半天才反应过来。有一类场景要单独处理前端只想拿 JSON、不想暴露 UI或者 UI 要挂到别的静态服务器上。这时候用starter-webmvc-api只出 JSON把 UI 那套静态资源单独部署再用springdoc.swagger-ui.url指过去。好处是文档站点和业务服务解耦业务服务可以干脆在生产环境关掉 doc 端点。4.3 WebFlux 栈的差异点切到 WebFlux依赖换成starter-webflux-ui注解那套基本不变但有两处必须提前知道。第一注解式 ControllerRestControllerGetMapping照常能扫到但函数式端点RouterFunctionHandlerFunction不在RequestMappingHandlerMapping里默认扫不出来。要用RouterOperation在配置类上手工补描述或者干脆把对外的接口统一写成注解式函数式的留在内部。这是选型阶段就该想清楚的事不是配错了能修的问题。第二路径前缀的来源不一样。Servlet 栈看server.servlet.context-pathWebFlux 看spring.webflux.base-path两个都配了的话文档里的路径可能和你以为的不一致。排查 404 的时候先确认这个前缀再去看paths-to-match——顺序反了会浪费很多时间。5. 避坑与排查五条踩过的记录这一章的五条都是「现象看着像 A实际是 B」的类型按现象、原因、解决三段记遇到时对着看就行。5.1 加了依赖但 /v3/api-docs 一直是 404现象依赖加了启动没报错浏览器打开/swagger-ui.html能跳转但页面空白F12 里看到请求/v3/api-docs返回 404。原因最常见的是项目里还残留着 springfox。两套库都会注册文档端点和静态资源处理器EnableSwagger2或者Docketbean 还在的话端点注册顺序会打架其次是spring.mvc.servlet.path或 context-path 改了前缀实际地址不是根路径。解决全局搜springfox和Docket把依赖、注解、springfox.documentation.*配置一起删干净别只删依赖留注解。然后确认server.servlet.context-path和spring.mvc.servlet.path有值就把它们拼到访问地址前面。迁移期可以临时把springdoc.api-docs.path改成/api-docs-probe这种明显不会撞的名字用来区分是「没生成」还是「路径被占」。5.2 启动报 Failed to start bean documentationPluginsBootstrapper现象Spring Boot 2.6 以上的项目启动阶段直接抛异常堆栈里能看到documentationPluginsBootstrapper或NullPointerException出现在路径匹配相关的类里。原因Boot 2.6 起 MVC 的默认路径匹配策略从AntPathMatcher换成了PathPatternParser早期版本的 springdoc 对后者的处理有问题扫描 handler 时拿到的匹配条件结构不一样。解决两条路优先升级 springdoc 到适配当前 Boot 版本的那一版确实不能升级的回退匹配策略spring: mvc: pathmatch: matching-strategy: ant_path_matcher回退是权宜之计ant_path_matcher在新版本里是逐步淘汰的能升级就别长期挂着。判断标准很简单升级只需要改版本号就升级升级要连带改一堆业务代码才考虑回退。5.3 泛型返回体在文档里变成了空对象现象接口返回ApiResultUserVO文档里data字段是空的或者 schema 名变成了ApiResultObject前端生成的类型里data是any。原因类型擦除加上 springdoc 的泛型推断没拿到实际类型。常见触发场景是返回类型声明成了父类或接口比如统一返回ResponseEntity?或者方法上用了ApiResponse但没写contentspringdoc 只能按声明的原始类型建模。解决先把返回类型声明成具体泛型别用通配符仍然不对的话在ApiResponse里显式指定 schema或者按 3.2 节的做法为高频泛型组合定义带Schema(name...)的子类。排查时打开/v3/api-docs直接搜 schema 名比在 UI 上猜快得多。5.4 接入 Spring Security 后 Swagger UI 白屏或 401现象加鉴权之前文档正常加了spring-boot-starter-security之后UI 页面加载出来了但样式全丢或者点 Authorize 之后所有请求还是 401。原因分两种。白屏是静态资源被拦了/swagger-ui/**和/webjars/**没放行全 401 是文档端点本身被拦了/v3/api-docs/**没放行或者 token 前缀处理不一致。解决在安全配置里给文档相关路径开白名单注意是四条路径而不是两条/v3/api-docs/**、/swagger-ui/**、/swagger-ui.html、/webjars/**。最后一条最容易漏漏了就是页面能开、样式全无。另外确认过滤器里的 token 解析逻辑接受Bearer前缀UI 发出去的请求带的就是这个格式。注意白名单只在开发环境开。生产环境更推荐整体关掉 doc 端点而不是放行。5.5 生产环境的 /v3/api-docs 把内部结构暴露了现象安全扫描报告里出现「接口文档未授权访问」点开一看全部接口路径、参数名、内部服务地址、示例里的测试账号都在里面。原因springdoc.api-docs.enabled默认是 true一路带到生产。demo 仓库为了演示方便通常全开着照抄配置就会带到线上。解决按 profile 关掉别靠注释代码# application-prod.yml springdoc: api-docs: enabled: false swagger-ui: enabled: false只关 UI 不关 api-docs 是没用的JSON 才是信息最全的那份。另外建议在 CI 里加一条检查生产 profile 下如果/v3/api-docs返回 200流水线直接失败。这条检查比任何口头约定都管用。6. 进阶把 openapi.json 拉进 CI 做契约校验文档能生成只是起点。真正让前端敢依赖它的是「接口改了会被发现」这件事。我的做法是在构建阶段把 openapi.json 导出来和仓库里的基线文件做 diff破坏性变更直接阻断合并。导出用官方 maven 插件挂在integration-test阶段因为它需要应用真的启动起来才能拉到 JSONplugin groupIdorg.springdoc/groupId artifactIdspringdoc-openapi-maven-plugin/artifactId version${springdoc.version}/version executions execution idexport-openapi/id phaseintegration-test/phase goalsgoalgenerate/goal/goals /execution /executions configuration apiDocsUrlhttp://localhost:8080/v3/api-docs/apiDocsUrl outputFileNameopenapi.json/outputFileName outputDir${project.build.directory}/outputDir /configuration /plugin插件本身不会启动应用所以要配合spring-boot-maven-plugin的start/stop两个 goal 一起用顺序是 start → generate → stop。端口冲突是这套配置最常见的失败原因CI 上给一个固定但冷门的端口别用 8080。导出来之后用一个几十行的脚本比对基线只看三类破坏性变更路径被删、操作被删、新增必填参数。# compare_openapi.py只拦破坏性变更新增接口不算 import json, sys def load(path): with open(path, encodingutf-8) as f: return json.load(f) base, cur load(openapi-baseline.json), load(target/openapi.json) broken [] for path, ops in base.get(paths, {}).items(): if path not in cur.get(paths, {}): broken.append(f路径被删除: {path}) continue for method, op in ops.items(): if method not in cur[paths][path]: broken.append(f操作被删除: {method.upper()} {path}) continue old_req {p[name] for p in op.get(parameters, []) if p.get(required)} new_req {p[name] for p in cur[paths][path][method].get(parameters, []) if p.get(required)} for name in new_req - old_req: broken.append(f新增必填参数: {method.upper()} {path} - {name}) print(\n.join(broken) if broken else no breaking change) sys.exit(1 if broken else 0)判断项就三行表写进流水线说明里团队看一次就懂校验项判断依据处理方式路径删除base 有、cur 无阻断合并操作删除同一路径下 method 缺失阻断合并新增必填参数required 集合变大阻断合并字段类型变更schema 的 type 不一致告警人工确认基线的维护方式是这套机制唯一的成本确认变更合理之后把新的 openapi.json 覆盖过去一起提交评审时看 diff 就能看出接口动了什么。这比在 PR 描述里写「改了几个接口」有用得多。血泪经验是版本号这件事——Info.version我一开始写死成v1再也没动过结果基线里所有变更都看不出是新版本还是误改diff 全靠人眼看路径。从那以后我每次发版都把 version 和 git tag 对齐再跑一遍导出和比对两个动作绑在一起做。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询