
简介围绕Spring Boot与OpenAPI 3文档化实践整理的示例工程合集面向具备Java基础、需要为REST接口生成规范说明和交互式文档的后端开发者。压缩包共219个文件含139个Java源码、23个XML配置、21个YAML文件及6个Markdown说明整体约281KB按示例模块组织便于逐项查阅复用。已有967人学习。内容覆盖springdoc-openapi依赖接入、全局信息与接口注解、GroupedOpenApi分组、安全认证描述及Swagger UI调试等场景可作为搭建API文档体系的参考模板也适合对照示例排查注解使用、分组配置与文档展示中的常见问题减少从零摸索成本。1. 先搞懂一件事接口文档为什么总是最先过期的那一份刚进项目组的新手大概率会遇到这样一幕前端同学在群里问「这个用户列表接口lastLoginTime 返回的到底是时间戳还是字符串」后端同学翻了两分钟代码回一句「等等我看下」。这种对话天天发生根源在于文档和代码是两份东西代码一改文档立刻变成历史遗迹指望人手动同步基本等于玄学。OpenAPI 3 就是给接口写说明书的一套标准格式而 springdoc-openapi 是那个照着你代码自动把说明书填好的工人配合 Spring Boot 用几乎不用额外付出。这份 springdoc-openapi-demos 演示仓库做的就是把这些工人干活的场景一个一个摆出来给你看一个最小的接口长什么样加了注解之后页面怎么变不同用法之间有什么区别。如果你刚学 Java 后端写过 Controller 但从没认真管过接口文档那这篇入门就是写给你的。2. 把两个名词说人话OpenAPI 3 是说明书格式springdoc 是写说明书的工人2.1 OpenAPI 3 到底是什么一份给机器看也给人看的菜单把接口文档想象成餐厅菜单。菜单上每道菜要写清楚叫什么名字、多少钱、有没有辣、要不要提前预订。接口也一样一个「查询用户列表」的接口得说清楚请求方法是什么、路径是什么、要不要传参数、返回的 JSON 里每个字段是什么类型、出错时返回什么。OpenAPI 就是规定「这份菜单该怎么排版」的标准只不过它规定的排版格式是 YAML 或者 JSON机器能读人也能读。一份最小的 OpenAPI 3 描述文件大概长这样# OpenAPI 3 描述文件的最小骨架 openapi: 3.0.1 # 规范版本3.x 和 2.0 是两套写法不能混 info: title: 用户服务接口 # 这份文档叫什么会显示在页面顶部 version: 1.0.0 # 接口版本不是代码版本 paths: # 所有接口路径都挂在 paths 下面 /api/users: get: summary: 查询用户列表 # 一句话说明这个接口干什么 responses: 200: description: 成功 # 返回值的描述真正返回结构在 content 里写看到这里你可能会想这不就是一份手写的 JSON 吗跟我有什么关系关键就在于这份文件本来是应该由人一个字一个字敲出来的接口有二十个、字段有上百个的时候手敲就是一场灾难。而且它最大的问题是会过期 —— 代码改了这份 YAML 没人改前端照着它联调直接翻车。所以 OpenAPI 的价值不在于「多了一种文档格式」而在于它是一份机器可以自动生成的格式。只要有个工具能读懂你的 Java 代码把代码里的路径、参数、返回值翻译成上面这种结构文档就永远不会和代码脱节。这个工具就是 springdoc-openapi。顺带提一句版本的事。你可能在老项目里见过 Swagger 2.0 这个词它是 OpenAPI 的前身格式和 OpenAPI 3 不一样注解包名也不一样。新手最容易犯的错就是把两套东西的依赖和注解混着用这个问题后面避坑那章会专门讲。2.2 springdoc-openapi 干的活读你的代码反推出一份说明书springdoc-openapi 的定位说白了就是一个翻译官。你的 Spring Boot 项目启动的时候它会把项目里所有带RestController、GetMapping这类注解的类扫描一遍从方法签名里推断出这是个 GET 请求、路径是/api/users、返回值是ListUser、User里面有 id、name、email 三个字段。推断完它把这些信息拼成 2.1 里那种 JSON挂在一个固定地址上默认是/v3/api-docs。这个地址返回的是纯 JSON人看着累。所以 springdoc 还顺手带了一个网页界面叫 Swagger UI地址通常是/swagger-ui/index.html。这个页面会把刚才那份 JSON 渲染成可以展开、可以点「Try it out」直接发请求的可视化文档。你什么都不用写加个依赖、启动项目页面就已经有了接口列表、参数、返回结构全在里面。我在第一次接触的时候最大的感受是「黑匣子感」消失了。以前接口文档是一个独立于代码之外的东西谁写的、什么时候写的、准不准全靠自觉。现在文档就是代码的投影代码是唯一真相文档是它照出来的一面镜子。镜子会不会照歪会比如你的返回值用了泛型嵌套、或者用了MapString, Object这种说不清结构的类型它就只能照出一个大概。但对于日常增删改查准确率已经足够高了。这里要区分清楚两件事springdoc-openapi 是库你把它加进自己的项目里它帮你生成文档springdoc-openapi-demos 是演示仓库里面是一个个已经配好的小工程用来演示这个库在不同场景下怎么用。新手常见的误解是把演示仓库当成库本身clone 下来一通改结果不知道自己改的是示例还是框架。2.3 演示仓库怎么读先找最小的那个别一上来就全量 clone打开这个演示仓库第一反应可能是「这么多工程我该看哪个」。给一个新手最省力的读法第一步不要 clone 整个仓库。仓库里每个演示工程都是独立的你只需要关心那个依赖最少、启动类最干净、只写了一两个接口的模块。判断标准很简单打开它的pom.xml如果里面只有 Spring Boot 的 web 依赖加上 springdoc 的依赖没有安全框架、没有数据库、没有消息队列那就是它。第二步把它单独下载下来先用mvn spring-boot:run跑起来什么都别改先确认能在浏览器里看到那个页面。这一步的意义是建立一个「正常长什么样」的基线后面你自己写代码出问题时才知道是哪里不一样了。第三步再回头去看它和你的项目差在哪。差在依赖差在注解差在配置演示工程的价值不是让你抄它的代码而是给你一个参照物让你在自己项目出问题时能快速做对比。提示读演示仓库的顺序建议是「能跑起来的最小示例 → 加了注解的示例 → 带安全认证或分组的示例」跳着看容易一头雾水。3. 为什么值得学三个真实到扎心的联调场景3.1 场景一改了一个字段名文档没人改前端白干半天假设你负责一个用户接口某天产品说要和国际规范对齐把返回字段userName改成username。你改完代码本地测试通过提交上线。三天后前端找过来说他那边一直取不到用户名页面上一片空白。问题出在哪前端是照着两周前那份手写的接口文档写的那份文档里还写着userName。文档是一份独立的 Word 或者 Markdown 文件躺在某个共享目录里没有任何机制提醒任何人去改它。这种事发生一次损失的可能是半天工时发生十次团队里就没人信文档了所有人改成「有问题直接问后端」。springdoc 改变的就是这个链条。它生成的文档不是独立文件是每次启动都从代码重新推导出来的。你把字段名改了重启项目页面上的字段名跟着变。前端只要打开那个地址看到的就是当下这一刻的真实情况不存在「文档过期」这个概念。3.2 场景二前端只能靠猜或者靠翻你的代码第二种情况更常见项目压根没文档。前端要对接一个新接口只能去翻后端代码或者干脆在群里问「这个参数必填吗」「传空字符串行不行」「分页是从 0 开始还是从 1 开始」。面试的时候很多人会说「我写过接口文档」实际工作中大部分团队的「文档」就是一句口头约定。这不能怪谁懒手写文档这事本身就反人性写完没人看改代码时又想不起来改它。用 springdoc 之后基础信息是自动的路径、方法、参数名、参数类型、是否必填取决于你有没有用RequestParam(required ...)、返回结构这些不需要你额外写一行。你唯一需要补的是那些代码表达不出来的业务含义比如「这个 status 字段0 是正常、1 是冻结、2 是注销」。这类信息用Schema或者Operation补一句描述就行工作量从「写一整份文档」降到「给关键字段加一句话」。3.3 场景三文档和代码是两个真相测试和运维都被坑第三种情况最隐蔽。接口文档看起来挺全但它记录的是三个月前的状态。新来的测试同学照着它设计用例测出来一堆「不符合预期」开发一看说「文档没更新现在不是这样了」。运维排查线上问题想确认某个接口的返回结构看了一眼文档被误导得更深。这背后的本质问题是当一份信息有两个来源就一定会分叉。代码是一个来源文档是另一个来源只要它们不是同源的早晚会不一致。springdoc 的思路是把文档变成代码的派生物让来源收敛成一个。这听起来是个小改动实际对协作效率的影响很大 —— 新人接手项目第一件事不是读代码而是打开那个页面点一遍十分钟就能把接口全貌摸清楚。3.4 顺带说说 springfox为什么新项目基本不选它了如果你搜资料一定会看到另一个名字springfox。它是上一代做同样事情的库很多老项目还在用。新手容易困惑这俩到底啥区别我该学哪个简单的判断是新项目直接用 springdoc老项目才需要考虑 springfox。原因是 springfox 对 Spring Boot 2.6 之后的版本兼容性出了名地差经常出现启动报错、页面 404 这类问题社区维护也慢下来了。springdoc 是后来者专门针对新版 Spring Boot 设计支持 OpenAPI 3 规范配置也更简单。但这不代表你可以无视 springfox 的存在。真实情况是很多公司项目还在用老版本你接手的时候会遇到一堆io.swagger.annotations.ApiOperation这种注解而 springdoc 用的是io.swagger.v3.oas.annotations.Operation。两套注解包名相似但完全不同混用会编译不过或者注解不生效。这个坑在第 5 章会细讲。4. 第一个例子跑通五步从空工程到能点的接口页面4.1 第一步准备环境建一个干净的 Spring Boot 工程前置条件只有两个装好 JDKSpring Boot 3 需要 JDK 17 及以上Spring Boot 2 用 JDK 8 也行装好 Maven 或者 Gradle。版本怎么看命令行敲一下# 查看 JDK 版本确认是 17 或以上 java -version # 查看 Maven 版本能跑就行 mvn -v然后建工程。最省事的方式是在 IDE 里用 Spring Boot 工程向导或者在官方脚手架页面填几项构建工具选 Maven语言 JavaSpring Boot 版本选一个稳定版依赖只勾 Spring Web。生成后解压用 IDE 打开确认能启动。这一步有个新手常犯的错勾了一堆依赖什么 Lombok、JPA、Security 全勾上。结果第一次启动就报错然后就卡在「到底哪里出问题了」。第一次跑通越干净越好只留 Spring Web其他的等文档页面出来了再加。4.2 第二步加依赖注意两个 artifactId 的区别打开pom.xml加 springdoc 的依赖。这里有一个分岔口选错了就直接 404!-- 在 properties 里声明版本方便统一管理 -- properties springdoc.version2.5.0/springdoc.version /properties dependencies !-- Spring Boot 3.x 用这个注意 artifactId 里带 starter -- dependency groupIdorg.springdoc/groupId artifactIdspringdoc-openapi-starter-webmvc-ui/artifactId version${springdoc.version}/version /dependency !-- 如果你是 Spring Boot 2.x要换成下面这个 artifactIdspringdoc-openapi-ui/artifactId 并且版本号要用 1.x 系列不能填 2.x -- /dependencies这三个参数各自是什么意思值得说清楚groupId固定是org.springdoc这是项目组织标识不会有第二种写法。artifactId是关键springdoc-openapi-starter-webmvc-ui这个名字里starter表示它是给 Spring Boot 3 用的Boot 3 之后官方推荐 starter 命名webmvc表示你的项目是传统的 Servlet 栈如果你用 WebFlux 响应式栈要换成带webflux的那一个ui表示它同时带上了 Swagger UI 那个网页界面 —— 如果只想要 JSON 不要页面去掉-ui就行。version一定要和 Spring Boot 大版本对应Boot 3 配 2.xBoot 2 配 1.x跨版本基本必炸。版本号我上面写了一个具体的数字作为示例实际用的时候建议去仓库页面看一眼当前最新的稳定版不用死记。4.3 第三步写一个最小的接口先不接数据库建一个UserController硬编码两条数据返回目的是把注意力全放在文档上不要被数据库配置分散package com.example.demo; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; RestController // 这个注解是必须的springdoc 靠它识别出这是接口类 RequestMapping(/api/users) // 统一路径前缀下面方法的路径会拼在它后面 public class UserController { // 内存里造两条假数据先不接数据库 private final ListUser users List.of( new User(1L, A同学, aexample.com), new User(2L, B同学, bexample.com) ); GetMapping // 对应 GET /api/users public ListUser list() { return users; } }再补一个User类型。这里用 Java 16 之后支持的 record一行就写完了package com.example.demo; // record 会自动生成构造方法、getter 和 equals字段结构一目了然 public record User(Long id, String name, String email) { }这段代码的逻辑很直白RestController告诉 Spring 这个类里的方法返回值直接作为响应体RequestMapping定了一个前缀路径GetMapping表示这个方法响应 GET 请求。springdoc 在启动时读到的信息就是有一个路径/api/users方法 GET返回一个数组数组元素有三个字段类型分别是数字、字符串、字符串。它不需要你额外告诉它这些全是从代码里推出来的。4.4 第四步启动项目访问两个地址用 IDE 的运行按钮或者命令行# 在项目根目录执行启动 Spring Boot mvn spring-boot:run看到日志里出现Tomcat started on port(s): 8080就说明起来了。然后打开浏览器试这两个地址第一个http://localhost:8080/v3/api-docs。你会看到一大坨 JSON别被吓到这就是 2.1 里说的那份「机器可读的说明书」。往下翻能找到paths下面挂着/api/users还有components里对User结构的描述。这份 JSON 才是真正的产物页面只是它的可视化。第二个http://localhost:8080/swagger-ui/index.html。这才是给人看的。页面上应该能看到一个分组里面列着GET /api/users点开可以展开看到返回结构右侧有个「Try it out」按钮点一下再点「Execute」能直接发出请求并看到真实返回。注意老资料里常说访问/swagger-ui.html新版里这个地址会自动跳转到/swagger-ui/index.html。如果你访问前者报 404直接试后者别急着怀疑依赖没加对。到这里第一个例子就跑通了。整个过程你没写一行文档页面已经出来了。这就是这个库最核心的价值 —— 先让你尝到甜头再谈怎么把它调得更准。4.5 第五步加第一个注解让页面从「能用」变成「能看懂」自动生成的内容有个短板全是技术信息没有业务含义。比如email字段页面上只写string不写它是登录邮箱还是备用邮箱。这一步就要靠注解补。import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; Tag(name 用户管理, description 用户的查询相关接口) // 给整个类打个标签页面上会分组显示 RestController RequestMapping(/api/users) public class UserController { Operation( summary 查询用户列表, // 显示在接口名旁边的短标题 description 返回内存中的全部用户仅用于演示不接数据库 // 展开后的详细说明 ) GetMapping public ListUser list() { return users; } }注解写完之后必须重启项目才生效 —— springdoc 是在启动阶段扫描注解的热更新有时候不重载这部分元数据改完看不到变化先重启再说。两个注解的作用分别是Tag作用在类上把同一类的接口归到一个分组页面上显示成「用户管理」Operation作用在方法上给单个接口补标题和描述。这两个是最常用的先把它们用熟其余的Parameter描述单个参数、Schema描述数据模型里的字段、ApiResponse描述各种返回状态码可以后面按需加。字段级的描述可以这么加import io.swagger.v3.oas.annotations.media.Schema; public record User( Schema(description 用户唯一标识, example 1) Long id, Schema(description 显示名称, example A同学) String name, Schema(description 登录邮箱, example aexample.com) String email ) { }description会显示在字段说明里example会填进「Try it out」的示例请求中前端拿这个示例一粘贴就能测。别小看这个 example联调时它能省掉大量「这个字段该传什么格式」的来回确认。5. 新手避坑清单五个最容易翻车的地方和它们的解法5.1 页面打不开浏览器一片 404现象项目启动没报错日志看着也正常但访问/swagger-ui/index.html就是 404或者访问/v3/api-docs返回一段错误 JSON。原因最常见的是依赖选错了。Spring Boot 3 的项目里加了 1.x 版本的springdoc-openapi-ui包的命名空间还是老的javax跟 Boot 3 用的jakarta对不上组件根本没注册进去。第二种可能是项目配置了统一的路径前缀比如server.servlet.context-path: /app那所有地址前面都得加上这个前缀你访问/swagger-ui/index.html自然是找不到的。解决先看pom.xml里 artifactId 和版本对不对Boot 3 必须是带starter的那个加 2.x 版本。再检查application.yml里有没有配context-path有的话访问地址要变成/app/swagger-ui/index.html。最后确认启动类的包路径能扫描到你的 Controller扫描不到的话连接口都不会有但页面本身还是能打开的 —— 如果页面都打不开问题一定在依赖或路径上。5.2 页面能打开但接口列表是空的现象Swagger UI 页面正常渲染标题也在就是下面一个接口都没有显示「No operations defined in spec」。原因三种可能。第一种Controller 类不在启动类所在的包或者子包下Spring 根本没扫描到它自然也不会被 springdoc 读到。第二种你用的是Controller而不是RestController或者方法上忘了加ResponseBodyspringdoc 认为这不是一个返回 JSON 的接口。第三种项目里引入了安全框架/v3/api-docs这个地址被拦截了springdoc 拿不到数据源页面就空了。解决先确认包结构启动类在最外层Controller 在它下面的子包里。再检查类上的注解。如果用了安全框架需要在配置里放行/v3/api-docs/**、/swagger-ui/**、/swagger-ui.html这几个路径。这一步新手特别容易漏因为页面能打开会让你误以为没被拦截实际上页面是静态资源JSON 接口才是被拦的那个。5.3 启动直接报错一堆 ClassNotFound 或者 NoSuchMethod现象加完依赖项目起不来了日志里一片红报ClassNotFoundException: javax.servlet.http.HttpServletRequest或者类似的方法找不到。原因这是典型的版本冲突。项目里同时存在 springfox 和 springdoc 两套库或者 Boot 3 项目里混进了基于javax命名空间的老包。Spring Boot 3 把整个生态从javax.*迁到了jakarta.*任何还依赖老命名空间的库都会在启动时炸掉。解决全局搜一下pom.xml和依赖树里有没有 springfox 相关的包有就排除掉。用mvn dependency:tree看一眼依赖树找到冲突来源。另外注意如果你是从老项目复制代码过来的代码里 import 的io.swagger.annotations.*也要全部换成io.swagger.v3.oas.annotations.*这两套注解长得像但不通用。5.4 注解明明加了页面上却一点变化都没有现象给方法加了Operation(summary ...)重启之后页面上还是原来的默认标题描述也没出现。原因头号原因是 import 错了包。IDE 的自动补全很容易帮你导进 springfox 那套老注解编译不报错但 springdoc 不认。第二个原因是注解加的位置不对 —— 如果你把接口定义在 interface 上、实现放在 impl 类里注解要加在 interface 的方法上加在实现类上会被忽略。第三个原因是改完没重启或者用了热部署工具但注解元数据没重新加载。解决点开 import 那一行看一眼包名必须是io.swagger.v3.oas.annotations。位置问题就统一约定注解写在对外暴露的那一层。改完老老实实停掉重启别指望热更新。这个坑我自己踩过不止一次每次都是「明明写了啊」最后发现是导错包。5.5 把它当成「写完代码就有了」忽略了它读的是代码本身现象接口返回的是一个MapString, Object或者一个泛型套泛型的结构页面上显示的是{}什么字段都没有。又或者返回结构里有一堆内部字段全暴露在文档上了。原因springdoc 是反推不是理解。它能推出来的前提是你的代码本身把结构写清楚了。MapString, Object这种类型在编译后没有任何字段信息它只能给你一个空对象。反过来如果你直接把数据库实体类当返回值所有字段包括内部标记位都会出现在文档上。解决返回值尽量用明确的类型别用 Map 糊弄。对外暴露的接口单独定义一套返回对象别直接复用数据库实体。如果确实有动态结构用Schema手动描述或者在配置里做过滤。这一点算是血泪经验自动生成不等于自动正确你喂给它的代码越规范它输出得越准。6. 下一步学什么从「能打开页面」到「能交付一份接口契约」跑通第一个例子之后很多人会停在「页面能打开就行」这个阶段。这其实只用了这个库两成的能力。往下走建议按这个顺序补阶段目标重点看什么第一周注解用熟Tag、Operation、Parameter、Schema、ApiResponse这五个覆盖日常九成场景第二周分组与配置按业务模块把接口拆成多个分组配置页面标题、版本、排序第三周与安全框架共存放行文档路径同时让文档能带上认证信息去调试需要登录的接口第四周契约意识理解这份 JSON 不只是给人看的还能被前端用来生成请求代码、被测试用来生成用例第一周的重点是把注解从「会写」变成「写得准」。具体做法是挑一个你手头真实的接口把它的每个参数、每种返回状态都补上描述然后打开页面以前端的视角读一遍 —— 如果你读完还是不知道某个字段该传什么说明描述没写到位。第二周开始接触分组。默认情况下所有接口挤在一个页面里项目稍大就找不着北。分组的方式是按业务模块拆比如用户、订单、支付各一组每组有自己的路径前缀配置起来不复杂但对可用性的提升很明显。第三周会碰到认证的问题。这时候你会发现页面上的「Try it out」发出去的请求是没带 token 的需要登录的接口一律返回未授权。解决方式是在配置里声明认证方式让页面右上角出现一个可以填 token 的按钮。这一步是很多教程跳过的地方但真实项目里绕不开。第四周是观念上的转变。这份自动生成的 JSON本质是一份接口契约前端可以拿它生成请求代码测试可以拿它生成用例甚至可以拿它做接口变更的比对看看这次上线改了哪些字段。到了这一步你就不再是「加了个文档插件」而是在用契约的方式管理前后端协作。我自己的习惯是每次提测前打开那个页面从头到尾点一遍所有接口确认参数说明、返回示例、错误码都是最新的。这个动作花不了十分钟但能挡掉大部分「联调时才发现字段对不上」的返工。这个习惯保持下来之后我再也没在群里被前端追着问过字段类型。希望帮到你。本文还有配套的精品资源点击获取