用SpringDoc替代Springfox,搞定Spring Boot API文档与OpenAPI 3集成

发布时间:2026/10/10 3:28:31
用SpringDoc替代Springfox,搞定Spring Boot API文档与OpenAPI 3集成
不少后端团队到现在还在用springfox搭的Swagger文档Spring Boot版本一升级文档项目直接罢工的情况我见过太多次了。与其在那折腾老框架的兼容性问题不如直接切换到SpringDoc这套方案。SpringDoc不是Swagger的替代品它承接着OpenAPI 3规范把文档生成、UI展示、权限配置全打通了而且和Spring Boot 2.x、3.x都配合得很顺畅。这篇文章不是给你复述官方文档而是把我实际迁移和日常使用中的思路、配置、踩坑点一次性讲清楚。不管你是刚接触API文档的新手还是被Springfox折磨过的老开发照着下面的步骤走基本都能把SpringDoc用顺。1. 为什么现在做API文档我更推荐SpringDoc1.1 老牌Springfox的尴尬处境Springfox是很多老项目里的文档标配基于Swagger 2规范生成接口文档配合springfox-swagger-ui展示页面早期确实好用。但问题在于维护节奏跟不上Spring生态的演进。Spring Boot 2.6之后Springfox就会因为路径匹配策略的变化出现各种兼容性问题更不用说Spring Boot 3全面切换到了Jakarta命名空间Springfox基本处于半停更状态继续在老项目里用每次升级依赖都像在赌运气。我在几个老项目里甚至遇到过这样的场景只是配合新需求升级了Spring Boot的patch版本结果文档直接打不开或者接口列表少了一大截。排查到最后发现是Springfox的底层匹配机制和新的Spring MVC配置冲突了。这种问题还不是改个配置就能解决往往需要动框架内部的兼容层投入产出比极低。1.2 SpringDoc到底解决什么问题SpringDoc的核心思路非常直接直接支持OpenAPI 3规范。OpenAPI 3相较Swagger 2在数据结构描述、请求体定义、安全认证声明等方面都做了大幅增强而SpringDoc就是帮你把Controller注解、Spring MVC的映射信息自动翻译成OpenAPI 3文档。它自带对Swagger UI的整合启动项目后访问一个URL就能看到交互式接口页面。从用户视角来看页面长得和以前很像但底层文档结构和能力完全不同了。另一个关键点是SpringDoc对Spring Boot版本的适配做得非常积极Spring Boot 3出来没多久SpringDoc的对应starter就同步更新了不会出现框架升级后文档模块掉链子的情况。1.3 它和Swagger官方工具的关系这里需要理清几个名词。Swagger这个词在不同语境下指代不同东西早期它是一个API文档工具套件的名称后来OpenAPI规范独立出来Swagger UI变成了展示OpenAPI文档的前端页面Swagger注解Api、ApiOperation这些则是老一代的注解体系。SpringDoc是Swagger UI的一个桥接器它负责把Spring应用的接口信息转成OpenAPI 3格式文档然后用Swagger UI把文档渲染出来。所以你可以理解为SpringDoc是引擎Swagger UI是仪表盘OpenAPI 3是数据协议。很多新手容易混淆的一点是用SpringDoc时你写的注解不再是Api、ApiOperation而是Tag、Operation、Schema这类新注解这一点迁移时要特别留意。提示SpringDoc也保留了部分兼容机制但建议新项目直接使用OpenAPI 3注解不要为了省事沿用旧的老注解长期维护会少很多麻烦。2. 从零集成SpringDoc先把基础文档跑起来2.1 依赖引入的版本对应关系SpringDoc的依赖坐标这几年发生过调整网上很多旧教程给的还是springdoc-openapi-ui这个坐标Spring Boot 3项目直接引入会报错。正确的对应关系如下场景依赖坐标版本说明Spring Boot 2.x项目springdoc-openapi-ui1.6.xSpring Boot 3.x MVC项目springdoc-openapi-starter-webmvc-ui2.xSpring Boot 3.x WebFlux项目springdoc-openapi-starter-webflux-ui2.xMaven工程在pom.xml里加这一段dependency groupIdorg.springdoc/groupId artifactIdspringdoc-openapi-starter-webmvc-ui/artifactId version2.3.0/version /dependencyGradle工程则这样配置implementation org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0确定版本号前建议先检查一下当前Spring Boot的版本SpringDoc的发布说明里会明确标注支持的Spring Boot版本范围别直接抄个版本号就完事。我习惯在项目的BOM或依赖管理里统一维护版本避免子模块各自为政。2.2 启动后第一时间该看哪几个地址依赖引入后什么都不用配置启动Spring Boot应用然后访问这几个地址文档页面http://localhost:8080/swagger-ui.html新版UI入口http://localhost:8080/swagger-ui/index.html原始OpenAPI JSONhttp://localhost:8080/v3/api-docs文档页面也可以这样访问http://localhost:8080/swagger-ui如果项目配置了server.servlet.context-path访问路径会相应变化比如context-path是/api那地址就是http://localhost:8080/api/swagger-ui.html。这里有个小细节/swagger-ui.html和/swagger-ui/index.html两个地址指向的其实是同一个页面区别在于一个带重定向、一个直接访问静态资源。我遇到过配置了Web安全拦截后/swagger-ui.html被拦截器挡掉但/swagger-ui/index.html却能正常访问的情况排查时可以考虑换个路径试一下。2.3 一个最小Controller演示文档自动生成SpringDoc最大的价值在于零侵入你的Controller不用继承任何基类、不用实现任何接口只要Spring MVC能识别这个接口SpringDoc就能自动提取信息。以下面的Controller为例RestController RequestMapping(/api/users) public class UserController { GetMapping(/{id}) public User getUser(PathVariable(id) Long id) { return new User(id, 示例用户); } PostMapping public User createUser(RequestBody User user) { return user; } }启动后打开Swagger UI就能看到两个接口的文档GET接口识别了路径参数idPOST接口识别了请求体User对象。SpringDoc甚至会自动解析User类的字段结构在文档里展示每个字段的类型、名称和位置要求。如果想让文档有更多描述信息给Controller和接口补充注解即可。这里不需要手动维护任何接口清单新增接口后文档自动同步这对接口数量多的项目来说省掉的工作量非常可观。3. 定制文档信息与注解使用技巧3.1 配置OpenAPI全局信息让文档有门面默认生成的文档页面顶部会显示OpenAPI默认标题如果你不想每个接口文档看起来都像贴牌产品最好定义一个OpenAPI的Bean把项目名称、接口描述、版本号、联系人信息都配置上去。这样团队内外的人打开文档第一眼就知道是哪个系统。示例配置如下Configuration public class OpenApiConfig { Bean public OpenAPI customOpenAPI() { return new OpenAPI() .info(new Info() .title(用户中心API文档) .description(用户中心对外提供的接口文档包含用户注册、登录、信息查询等能力。) .version(v1.0.0) .license(new License().name(内部使用))) .externalDocs(new ExternalDocumentation() .description(在线运维手册) .url(https://example.internal/docs)); } }这里把OpenAPI对象当成一个普通的Spring Bean来管理SpringDoc启动时会自动读取并塞进生成的文档里。如果你有多个环境测试环境、预发布环境可以让配置文件里的信息动态填充比如从yml读取version字段。3.2 常用的注解看这一张表就够了从Springfox迁移过来最容易乱的就是注解。老写法是Api和ApiOperationSpringDoc理念下的新注解是Tag和Operation。两者功能接近但命名和参数设计完全不同。用途旧注解Swagger 2新注解OpenAPI 3Controller分组Api(tags 用户管理)Tag(name 用户管理, description ...)接口说明ApiOperation(value 创建用户)Operation(summary 创建用户, description ...)参数说明ApiParam(用户ID)Parameter(description 用户ID)模型属性说明ApiModelProperty(用户名)Schema(description 用户名)响应状态说明ApiResponse(code 404, message 没找到)ApiResponse(responseCode 404, description 没找到)实际代码中我常用的写法是这样Tag(name 用户管理, description 用户相关接口) RestController RequestMapping(/api/users) public class UserController { Operation(summary 查询用户详情, description 根据用户ID获取用户基本信息) Parameter(description 用户ID, example 10001) GetMapping(/{id}) public User getUser(PathVariable(id) Long id) { return new User(id, 示例用户); } }这里有个容易忽略的点Operation的summary字段会展示在接口列表的标题位置description则在展开后显示。如果接口很多建议在summary里写一句话能看懂的概括而不是写长篇大论这样接口列表的观感会清晰很多。3.3 分组配置按模块拆文档各看各的许多项目不止一个业务模块用户服务、订单服务、支付服务都放在同一个应用里时所有接口混在一起文档会非常冗长。SpringDoc提供GroupedOpenApi可以按包路径或注解标识把接口拆分成多个分组Swagger UI顶部会出现下拉框切换分组。Bean public GroupedOpenApi userApi() { return GroupedOpenApi.builder() .group(用户服务) .packagesToScan(com.example.controller.user) .pathsToMatch(/api/users/**) .build(); } Bean public GroupedOpenApi orderApi() { return GroupedOpenApi.builder() .group(订单服务) .packagesToScan(com.example.controller.order) .pathsToMatch(/api/orders/**) .build(); }配置完成后Swagger UI页面顶部会多一个下拉框可以切换用户服务、订单服务的文档视图。这个配置本质上是在生成文档时做了一层过滤不影响接口实际访问。微服务架构下如果通过网关聚合多个服务的文档也可以在网关层按服务名做分组展示效果是一样的逻辑。3.4 模型类注解让请求响应结构清晰可读SpringDoc会反射解析DTO的结构但如果你不补充注解文档里的字段描述就是空白。为了让对接方看得明白给DTO的字段加上Schema注解非常有必要。public class User { Schema(description 用户唯一ID, example 10001, requiredMode Schema.RequiredMode.REQUIRED) private Long id; Schema(description 用户名, example zhangsan) private String name; Schema(description 邮箱地址, example zhangsanexample.com) private String email; }requiredMode字段用于标记该字段是否为必填SpringDoc会把它映射到OpenAPI文档的required数组里。如果参数校验用了javax.validation或jakarta.validation的NotNull、NotBlank注解SpringDoc也能自动识别并标记为必填不需要额外手写。泛型包装类在文档里容易被解析成一堆复杂嵌套结构这里给出一个实用的处理办法Schema(description 分页返回结果) public class PageResultT { Schema(description 当前页码) private Integer pageNum; Schema(description 每页数量) private Integer pageSize; Schema(description 总记录数) private Long total; Schema(description 数据列表) private ListT list; }配合SpringDoc对泛型天然支持的特性实际返回PageResult 时文档里能准确列出list字段下是User对象的具体结构。这一点比早期的Springfox要靠谱很多Springfox在处理嵌套泛型时经常退化成Object。4. 给文档加上认证鉴权配置4.1 为什么文档页面需要配置安全方案接口文档如果涉及需要登录才能访问的接口在Swagger UI页面直接点击“Try it out”执行请求时会因为缺少认证信息而得到401错误。Swagger UI本身支持在页面上设置认证信息点击右上角的Authorize按钮填入token后后续请求就会自动携带认证凭据。关键是后端需要告诉Swagger UI这个接口支持哪种认证方式这就要靠SpringDoc的SecurityScheme配置来暴露。最常见的场景是JWT认证通过Header传递token配置如下Bean public OpenAPI customOpenAPI() { return new OpenAPI() .info(new Info().title(用户中心API).version(v1.0.0)) .components(new Components() .addSecuritySchemes(bearer-jwt, new SecurityScheme() .type(SecurityScheme.Type.HTTP) .scheme(bearer) .bearerFormat(JWT))) .addSecurityItem(new SecurityRequirement().addList(bearer-jwt)); }配置完成后Swagger UI页面右上角会出现Authorize按钮点击后输入token之后所有标注了安全要求的接口在Try it out时都会自动加上Authorization请求头。这里addSecurityItem是把安全要求加到全局也就是默认所有接口都需要认证如果个别接口匿名可访问可以在接口上用SecurityRequirement空列表覆盖。4.2 APIKey场景的配置方式很多内部系统用的是自定义请求头传递token比如X-Auth-Token这种。这种场景下SecurityScheme要定义成APIKey类型Bean public OpenAPI customOpenAPI() { return new OpenAPI() .info(new Info().title(内部管理系统).version(v1.0.0)) .components(new Components() .addSecuritySchemes(api-key, new SecurityScheme() .type(SecurityScheme.Type.APIKEY) .in(SecurityScheme.In.HEADER) .name(X-Auth-Token))) .addSecurityItem(new SecurityRequirement().addList(api-key)); }type选择APIKEYin指定HEADERname指定请求头名称。配置后Swagger UI会弹出输入框让你填X-Auth-Token的值。这里注意in也可以设置在COOKIE但浏览器跨域场景下携带cookie会有诸多限制实际使用中还是header最常见。4.3 多认证方式共存的处理思路有些项目同时支持token认证和基础认证可以在components里声明多个SecurityScheme然后在接口上用SecurityRequirements注解指明不同接口采用哪种方式。Operation(summary 使用token访问) SecurityRequirement(name bearer-jwt) GetMapping(/token-access) public String tokenAccess() { return ok; } Operation(summary 匿名访问) SecurityRequirement(name ) GetMapping(/anonymous) public String anonymous() { return ok; }这里SecurityRequirement(name )表示清空该接口的安全要求SpringDoc解析后该接口在文档中不再被标记为需要认证Swagger UI在Try it out时就不会自动携带Authorization头。多方案配置的细节较多建议对照Swagger UI渲染出来的效果逐步调整确认Authorize按钮的行为符合预期。5. 生产环境如何安全地开关文档5.1 用配置文件控制文档的启停Swagger文档展示的接口信息对开发测试非常有用但部署到生产环境后如果接口文档直接暴露在公网等于把所有接口路径、参数结构、字段含义全透明公开了安全隐患不小。好在SpringDoc可以通过配置项动态关闭文档。在application.yml中springdoc: api-docs: enabled: false swagger-ui: enabled: false这两项设置为false后/v3/api-docs和/swagger-ui.html都会返回404。更推荐的做法是结合Spring Profile配置在开发环境开启生产环境关闭。# application-dev.yml springdoc: api-docs: enabled: true swagger-ui: enabled: true# application-prod.yml springdoc: api-docs: enabled: false swagger-ui: enabled: false这样本地开发和联调时能正常看文档生产环境则完全屏蔽文档入口。实际部署时还能通过环境变量动态覆盖比如在容器平台设置SPRINGDOC_SWAGGER_UI_ENABLEDfalse灵活度非常高。5.2 结合Spring Security做访问控制除了直接关闭文档另一种常见做法是保留文档但要求认证后才能访问。比如只在测试环境开放文档同时要求只有内网或特定角色才能查看。在Spring Security的过滤链配置中给文档相关路径单独配置权限规则Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth - auth .requestMatchers(/swagger-ui/**, /v3/api-docs/**).hasRole(ADMIN) .anyRequest().authenticated() ) .httpBasic(); return http.build(); }这样配置后任何人访问文档页面都要先登录而只有ADMIN角色可以看到文档内容。这里要注意Spring Security的拦截规则顺序非常重要更具体的路径规则要放在前面否则会被anyRequest吞掉。5.3 文档信息的安全粒度把控有些项目不能全部关闭文档但又不希望某些内部接口被外部看到。SpringDoc的分组能力在这里就能派上用场可以把对外接口放在一个分组内部接口放在另一个分组然后给内部接口分组做访问限制。Bean public GroupedOpenApi internalApi() { return GroupedOpenApi.builder() .group(内部接口) .pathsToExclude(/api/public/**) .build(); }pathsToExclude是个很好用的配置可以在分组内排除指定路径。配合上面的Security策略你可以让内部接口分组只允许特定IP段访问或者直接不开放这个分组。文档的信息安全本质上是一个综合性问题SpringDoc能做的只是从接口文档层面提供控制手段真正的接口权限还是要在业务代码里做好。6. 高频问题与排查实录6.1 文档页面404先检查依赖和路径集成SpringDoc最容易碰到的麻烦就是访问/swagger-ui.html显示404。出现这个问题的排查顺序我认为应该固定下来第一确认依赖坐标是否正确Spring Boot 3项目用了springdoc-openapi-ui这种旧坐标类都加载不出来页面自然404。第二确认配置里有没有误关闭文档检查springdoc.swagger-ui.enabled和springdoc.api-docs.enabled是否被设置成了false。第三看项目里有没有自定义路径匹配策略SpringBoot 2.6之后默认的PathPatternParser和SpringDoc的适配需要特定版本支持老版本SpringDoc搭配新Boot版本会导致路径匹配异常。我在一个项目里排查过类似的诡异问题SpringDoc依赖正常、配置也正常但访问带context-path的地址时文档能打开不带context-path就是404。后来发现是网关转发时把前缀路径截掉了导致静态资源请求转发到了错误的映射上。所以遇到404问题时把SpringBoot启动日志里的路径映射打出来看一眼往往能快速定位。6.2 注解不生效多半是位置或命名空间搞错了Operation写了summary文档上却不显示这种情况要么是注解导包导错了要么是注解放的位置不对。OpenAPI 3的Operation应该放在方法上Tag放在类上。导包时注意别混用org.springdoc.api.annotations.ParameterObject是一个io.swagger.v3.oas.annotations.Parameter是另一个两者适用场景有区别混用会看到文档解析异常。还有一个比较容易踩的坑把Tag直接写在方法上而不是类上。SpringDoc对Tag的处理是类级别的分组标签如果放在方法上可能不会按预期显示在分组区域里。实际看到的表现是接口列表里没有分组名或者分组错乱。遇到注解不生效的问题优先检查包名和注解位置。6.3 从Springfox迁移过来的兼容问题老项目从Springfox往SpringDoc迁移最直观的问题是注解类全变了代码里大量的Api、ApiOperation、ApiModelProperty需要手动替换成Tag、Operation、Schema。如果项目接口数量特别多纯手工改很痛苦需要花时间做批量替换时注意别误伤同名注解。比如ApiResponse注解在新旧两套体系里都有但包路径不同一个是io.swagger.annotations.ApiResponse另一个是io.swagger.v3.oas.annotations.responses.ApiResponseresponseCode字段从字符串改成了字符串新注解是String类型code字段变成responseCode。迁移时IDE的全局替换功能可以处理一部分但建议替换后逐个接口抽查特别是泛型返回和复杂嵌套对象最容易出现文档解析偏差。6.4 文档显示正常但Try it out请求失败页面能打开接口列表清清楚楚点击Try it out却报错这通常是跨域问题或安全配置拦截。先看控制台报错是不是403或401如果是说明请求被Spring Security拦截需要在过滤链里放行OPTIONS请求或给文档调试接口加上匿名访问权限。如果是跨域问题浏览器控制台会给出明确的CORS错误。SpringDoc本身不解决跨域你需要自己配置CORS。一种方案是使用Spring MVC的CorsFilter一种是在网关层统一处理跨域。Swagger UI发出的请求默认带Content-Type: application/json如果接口的CrossOrigin只配置了简单请求的来源预检请求会被拦掉。6.5 泛型类型文档显示不全或变成Object这是Springfox时代的老大难问题SpringDoc对泛型的支持已经好了很多但个别情况下仍会遇到类型擦除导致的解析问题。比如返回Result 这种统一包装类型时文档里T的位置显示成空或者object。解决办法是配合Schema的implementation属性显式指定类型Operation(summary 分页查询用户) GetMapping(/page) public ResultPageResultUser page() { return Result.ok(new PageResult()); }如果这样写后文档仍然无法解析出User结构可以考虑用ApiResponse的content属性补充描述或者调整Result 的泛型声明方式确保源码里类型信息是可追踪的。另外使用部分泛型类型比如裸类型会让SpringDoc无从推断尽量别在方法签名里写裸类型。6.6 常见问题速查表异常表现可能原因解决思路/swagger-ui.html 404依赖坐标对应错、文档被禁用、路径映射冲突检查依赖版本确认配置文件查看日志路径映射文档页面能开但无接口接口类扫描路径不对、分组配置错误检查组件扫描范围确认GroupedOpenApi的packagesToScan注解不生效导包路径错误、注解位置放错统一使用io.swagger.v3.oas.annotations包方法级用OperationTry it out返回401安全方案未配置、接口确实需要认证配置SecurityScheme或关闭该接口安全要求泛型返回解析异常类型擦除、包装类设计不合理显式指定implementation或优化泛型层次页面样式错乱静态资源被拦截、CDN路径不可达检查安全策略放行/swagger-ui/**静态资源配置本地方资源7. 几步升级路径与个人实战建议7.1 从Springfox迁移三步走如果项目里目前还在用Springfox我建议分三步完成升级不要一把梭第一步引入SpringDoc依赖同时保留Springfox依赖让两套文档共存几周新旧文档对照着看接口数量和信息完整性。这一步主要是验证SpringDoc对现有接口的解析效果尤其关注泛型、继承、嵌套对象这些复杂场景。第二步把Controller里的Swagger注解迁移到OpenAPI注解。这一步工作量最大建议按模块分批推进每迁移一个模块就对比一次新文档的展示效果确认接口数量、字段描述、分组信息都没问题再继续。第三步确认所有接口迁移完成后移除Springfox依赖和相关配置类清理配置文件里的Swagger相关项。这一步要检查是否还有别的地方引用了springfox的类防止编译报错。7.2 接口注释里写人话比堆注解更重要注解是给机器看的元数据描述信息是给人看的。我见过太多团队文档里接口描述全是代码注释复制过来的半截话看着跟猜谜一样。建议Operation的summary控制在20字以内说清楚这个接口干什么description里写清楚核心业务规则、必填参数、典型报错场景。这样对接方不用翻代码就能把接口用明白。尤其在跨团队协作和外部对接场景下接口文档几乎就是唯一的技术契约。字段的example值能填就填Swagger UI展示时会有个示例值对接方可以直接复制使用省去大量“这个字段传什么”的沟通成本。7.3 文档也是要复查的自动生成的文档不代表不需要维护接口参数、返回结构改了文档虽然会自动同步但描述信息和示例值不会自动更新。我建议把文档检查纳入代码评审的常规项改动接口时顺手看一眼生成后的文档展示效果确认没有过时描述或者错误示例。另外SpringDoc版本升级也不要无脑追最新。它和Spring Boot、Spring Security、Swagger UI三方都有版本关联升级前看一眼release note确认适配的Boot版本范围。我一般保持SpringDoc和Spring Boot同步升级不单独拧一个依赖的版本。个人体会是SpringDoc这套工具链的核心价值在于把接口文档从“人工维护的Word文件”变成了“代码的一部分”。只要注解写得规范文档质量就和代码质量绑定在一起代码烂文档就烂代码好文档就好。实际开发和联调阶段Swagger UI上的Try it out功能也确实是接口自测的一把好手用了之后就很难再回到手工拼请求去测接口的日子了。