SpringBoot3.x 一升级,最先炸的往往不是业务代码,而是接口文档。以前那套 springfox + Swagger2 的组合,在 SpringBoot 2.6 时代就开始频繁出幺蛾子,升级到 3 之后直接不可用。开发群里隔三差五就有人甩出启动报错截图问:为什么我的 Swagger 一启动就 NullPointerException?为什么 SpringBoot3 里接口文档页面打不开?
这篇就是把 SpringBoot3.x 整合 Swagger 这条线彻底捋一遍。我会从选型原因、版本对应关系、核心配置、进阶玩法到高频踩坑,按真实项目推进的顺序写,争取让刚接触 SpringBoot3 的初学者能直接照着复制粘贴,也让已经在迁移老项目的同学少走几个弯路。
1. 为什么SpringBoot3.x时代Swagger整合一定要换方案
1.1 Springfox兼容性问题的根源
先说说老方案为什么不行了。Springfox 这套工具的底层实现严重依赖 Spring MVC 的路径匹配机制,它通过扫描 HandlerMapping 来推断接口信息。SpringBoot 2.6 开始,默认的路径匹配策略从AntPathMatcher切换到了PathPatternParser,Springfox 的解析逻辑直接踩空,大量项目升级后就遇到NullPointerException,最典型的报错就是Failed to start bean 'documentationPluginsBootstrapper'。
到了 SpringBoot3.x 事情更麻烦了。SpringBoot3 基于 Jakarta EE 9,所有javax.*命名空间全部换成jakarta.*,Springfox 里所有依赖 javax 包的代码全部失效。就算你用各种兼容手段强行绕过路径匹配的问题,注解、反射、类加载层面的报错也根本清不完。说白了 Springfox 项目本身已经处于停滞状态,它就没有为 Jakarta 和 SpringBoot3 大版本调整做过适配。
所以 SpringBoot3.x 项目里继续用 Springfox 属于给自己挖坑。正解是换成 springdoc-openapi,这才是 SpringBoot3 生态里被广泛验证过的方案。有人纠结为什么不是 Swagger 官方工具,其实 springdoc-openapi 走的就是 OpenAPI 3 规范,生成的文档 JSON 完全兼容 Swagger UI,只是解析和装配机制换成了适配 SpringBoot3 的实现。
1.2 springdoc-openapi为什么能上位
springdoc-openapi 能在 SpringBoot3 时代被普遍接受,核心原因是它长在 SpringBoot3 的机制上。它自己实现了基于 Spring Boot 自动配置的文档装配器,能够扫描@RestController、@RequestMapping、@GetMapping这些注解体系,而不是像 Springfox 那样靠自定义 BeanFactoryPostProcessor 去 hack Spring MVC 的底层结构。所以它天然适配新的路径匹配策略,也天然兼容 Jakarta 命名空间。
另外 springdoc-openapi 对开发体验的补强也是实打实的。它能自动聚合application.yml里的配置项,支持多分组、支持 Only 和忽略某些路径、支持跟 Spring Security 直接打通。实测下来,SpringBoot3.3/3.4 配 springdoc-openapi 2.6 以上版本,基本零额外配置就能把在线文档跑起来。
注意:如果你的项目里还留着 springfox 的
@EnableSwagger2、DocketBean 或者io.swagger.annotations.*包的老注解,升级后第一时间全删掉。这俩套体系混用,启动不报错都算运气好。
1.3 版本对应关系速查
网上有很多版本对应关系表,但很多都过时了。这里给一份我实际验证过的对应方案:
| SpringBoot版本 | JDK版本 | springdoc-openapi推荐版本 | 说明 |
|---|---|---|---|
| 3.0.x | 17+ | 2.1.x | 能用但偏老,不推荐新项目 |
| 3.1.x | 17+ | 2.2.x | 稳定,常见于过渡期项目 |
| 3.2.x | 17+ | 2.3.0+ | 必须 2.3.0 以上,否则有兼容问题 |
| 3.3.x / 3.4.x | 17+ | 2.6.x | 当前比较稳的组合 |
| 3.5.x | 17+ | 2.6.x / 2.8.x | 建议用最新 2.x |
版本选择的核心原则很简单:尽量用 2.x 里最新的稳定版,别拿 1.x 的教程硬套 SpringBoot3,也别用 2.1.0 去跑 SpringBoot3.3。springdoc 官方对不同 SpringBoot 小版本做了针对性适配,但你不需要记住每个补丁的差异,只要保证依赖版本不是落后太多就行。我自己现在的标准配置是 SpringBoot 3.3.x + springdoc-openapi 2.6.0,用了一个季度没出过兼容性问题。
2. 整合前的准备工作与技术选型
2.1 JDK与SpringBoot版本前置条件
SpringBoot3.x 硬性要求 JDK17 及以上。别指望在 JDK8 环境里跑 SpringBoot3,启动阶段直接不认 class 文件版本。如果你的项目还停留在 JDK8,那第一步是先做 JDK 升级,把pom.xml里的java.version改成 17,同时把编译器插件版本提到 3.8.1 以上。
这里有个容易忽略的坑:JDK17 之后,--illegal-access管控收紧,一些依赖里用反射强行访问 JDK 内部类的库会直接炸。好在 springdoc-openapi 本身没有这种恶习,只要你的项目不是重度依赖 CGLIB 代理+内部类访问,基本不会有额外障碍。真碰到了,优先查是否还有老版本缺省依赖混在编译链里,比如某些低版本cglib。
另外 IDE 也要跟着调。我在实际开发中遇到过同事代码没问题但是在 IDEA 里启动提示java: 错误: 无效的源发行版: 17,大概率是 IDE 的字节码版本没跟项目 SDK 对齐,把 Project Structure 里的 SDK 和 language level 都设成 17 就能解决。
2.2 Maven依赖的选型与版本避坑
SpringBoot3.x 整合 Swagger 需要的核心依赖只有一个,就是 springdoc-openapi 的 starter。选择 webmvc 还是 webflux 版本,取决于你项目的 web 栈。绝大多数后端项目用的是 Spring MVC,对应引入:
<dependency> <groupId>org.springdoc</groupId> <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId> <version>2.6.0</version> </dependency>如果你的项目是 WebFlux 响应式栈,把 artifactId 换成springdoc-openapi-starter-webflux-ui。这两个别混用,混用会导致文档装配器重复或者启动期报BeanDefinitionStoreException。
引入依赖的时候注意一个点:springdoc-openapi starter 会传递依赖引入swagger-core、swagger-models、swagger-annotations等组件。如果项目里之前为了兼容 Springfox 手动加过io.springfox的依赖,升级后必须用依赖管理把 springfox 整个排除掉,否则两个注解体系的注解同时在 classpath 下,扫描器容易把文档解析到旧注解上,导致接口描述和实际接口对不上。
可以用下面命令自查依赖树,发现springfox字样就排除:
mvn dependency:tree -Dincludes=io.springfox如果项目里实在改不动依赖树,就用 Maven 排除嵌在传递链里的 springfox:
<dependency> <groupId>org.springdoc</groupId> <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId> <version>2.6.0</version> <exclusions> <exclusion> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> </exclusion> </exclusions> </dependency>2.3 最简启动:默认配置能干什么
依赖加完之后,其实不需要任何额外配置就能跑起来。启动 SpringBoot 应用,浏览器访问两个地址:
- 接口文档 JSON:
http://localhost:8080/v3/api-docs - Swagger UI 界面:
http://localhost:8080/swagger-ui/index.html
期间不需要加@EnableSwagger2、不需要配置 Docket Bean,springdoc-openapi 会自动扫描应用上下文里的 Controller 和接口注解。如果你只想“让文档先出来”,这时已经完成了。
不过默认状态有两个体验不足的地方:一是文档标题和描述是空白的,二是所有接口没有任何中文字段说明,因为代码里没写描述注解。而这些恰恰是 Swagger 整合里最能体现工作量的部分,下一节细讲。
如果你的项目设置了
server.servlet.context-path,比如/api,那么 Swagger 路径也要带上上下文,访问地址变成http://localhost:8080/api/swagger-ui/index.html。
3. 核心配置与文档定制
3.1 OpenAPI信息配置类的标准写法
要让文档看起来像个正经产品文档而不是临时调试页,第一件事是写一个 OpenAPI Bean,把文档标题、描述、版本号、联系方式填上。这是每个 SpringBoot3 + Swagger 项目都要做的基础动作:
package com.example.config; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.info.Contact; import io.swagger.v3.oas.models.info.Info; import io.swagger.v3.oas.models.info.License; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class OpenApiConfig { @Bean public OpenAPI customOpenAPI() { return new OpenAPI() .info(new Info() .title("某业务系统 API 文档") .description("这里可以写接口文档的适用范围、对接约定、基础说明等") .version("v1.0.0") .contact(new Contact() .name("后端研发组") .email("backend@example.com")) .license(new License() .name("Apache 2.0") .url("https://www.apache.org/licenses/LICENSE-2.0"))); } }写配置类时最需要注意的 import 路径。SpringBoot3 时代务必要用io.swagger.v3.oas.models.*这套包,而不是springfox.documentation.service.*。很多从旧项目抄配置的同学,把Docket、ApiInfo这些类名直接搬到新项目里,编译期就报红。springdoc 的等价物是OpenAPI和Info,一个管装配一个管信息。
这段配置上线之后,Swagger UI 右上角会显示项目名称和版本号,页面上方的描述区域也会出现你填写的文字。接口列表仍然自动扫描,暂时还没有额外注解。
3.2 Controller与实体的注解规范
springdoc 能自动扫描接口,但你要是想让每个接口的用途、参数、响应说明清晰,就得配合 OpenAPI 3 的注解。常用的注解类集中在io.swagger.v3.oas.annotations.*下,核心分类如下:
@Tag:标注在 Controller 类上,相当于给接口模块起分组名,Swagger UI 左侧的目录就是按它来的。@Operation:标注在接口方法上,描述单个接口的用途、请求响应细节。@Parameter:标注在方法参数或参数上,描述单个参数的用途。@Schema:标注在实体类或字段上,描述请求/响应体的结构。@ApiResponse:标注响应状态码含义,可以定义多个。
一个比较标准的 Controller 写法长这样:
package com.example.controller; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.responses.ApiResponse; import org.springframework.web.bind.annotation.*; @Tag(name = "用户管理", description = "用户信息查询与管理接口") @RestController @RequestMapping("/api/user") public class UserController { @Operation(summary = "根据ID查询用户", description = "根据用户ID获取用户基本信息") @ApiResponse(responseCode = "200", description = "查询成功") @GetMapping("/{id}") public UserVO getUser( @Parameter(description = "用户ID", required = true, example = "1") @PathVariable("id") Long id) { // 实际业务逻辑 return new UserVO(); } @Operation(summary = "新增用户", description = "创建一条新的用户记录") @PostMapping public UserVO createUser(@RequestBody UserCreateDTO dto) { // 实际业务逻辑 return new UserVO(); } }实体类上的字段说明用@Schema标注:
package com.example.vo; import io.swagger.v3.oas.annotations.media.Schema; @Schema(description = "用户信息响应对象") public class UserVO { @Schema(description = "用户ID", example = "1") private Long id; @Schema(description = "用户名", example = "zhangsan") private String name; @Schema(description = "手机号", example = "13800000000") private String phone; // getter / setter 省略 }example字段值得多说一句。给字段写上合理的示例值,Swagger UI 的Try it out功能里就会自动预填,联调时测试效率高很多。我之前一个项目里所有接口都规范补了example,前端拿文档调接口基本不用自己在页面上敲测试数据,省了特别多沟通成本。
3.3 利用分组管理接口模块
项目接口多了,所有 Controller 混在一个文档里非常难找。springdoc 支持按分组切分接口列表,有两种常见姿势。
第一种是用配置项,在application.yml里定义多个 group:
springdoc: group: configs: - group: user paths-to-match: /api/user/** packages-to-scan: com.example.controller.user - group: order paths-to-match: /api/order/** packages-to-scan: com.example.controller.order配置完重启,Swagger UI 左上角会多一个下拉框,切换 group 只显示对应的接口集合。packages-to-scan和paths-to-match可以同时存在,作用是“与”的关系,也就是说两者都命中时接口才会归入该分组。
第二种姿势是声明GroupedOpenApiBean,适合想通过 Java 代码做更复杂逻辑分组的情况:
@Bean public GroupedOpenApi userGroupApi() { return GroupedOpenApi.builder() .group("用户端接口") .pathsToMatch("/api/user/**") .packagesToScan("com.example.controller.user") .build(); }分组命名尽量用中文可读名,因为前端同事打开下拉框时看到的是直接展示的元信息。另外分组还会影响文档的分发场景,比如只给 App 端开放的接口一个分组,给管理后台一个分组,算是低成本实现接口权限边界的一种方式。
3.4 全局参数与公用Header处理
很多后端项目里,接口设计会要求请求头带上某种令牌、追踪 ID 或者租户标识。普通接口可以逐个加@Parameter,但如果所有接口都要带同一个 Header,逐个加注解既容易漏又难维护。更推荐的做法是在 OpenAPI 配置里声明全局参数。
import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.media.StringSchema; import io.swagger.v3.oas.models.parameters.Parameter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class OpenApiHeaderConfig { @Bean public OpenAPI globalHeaderOpenAPI() { return new OpenAPI() .components(new Components() .addParameters("X-Trace-Id", new Parameter() .in("header") .name("X-Trace-Id") .description("全链路追踪ID") .required(false) .schema(new StringSchema()))) .info(new io.swagger.v3.oas.models.info.Info() .title("全局Header示例文档") .version("v1.0.0")); } }需要说明的是,只声明全局参数并不会让所有已有接口自动关联这个参数。要让参数真正出现在每个接口的调用说明里,还需要配合自定义注解或者对接口路径做统一的参数注入。一个简单可行的方案是用拦截器的思路,在后端业务层强制读取 Header,同时文档层面在所有接口描述中统一说明该 Header 为必填。这样至少让前端知道调用时必须带。
真正让 Header 出现在所有接口的 Parameters 区域,可以借助springdoc-openapi的OpenApiCustomizer,在文档生成后对每个 Operation 追加参数。这个属于进阶玩法,后面实际操作时如果大家需要再展开,普通项目用配置 + 文档约定就够用。
4. 真实项目中的进阶整合要点
4.1 携带Token鉴权:Swagger UI的Authorize按钮怎么配
现在后端接口基本都有登录态校验,最常见的就是 JWT Token 放在请求头Authorization: Bearer xxx。如果不做配置,你在 Swagger UI 上打开需要校验的接口,Try it out点了会被拦截器拦截返回 401。
springdoc 可以声明全局安全配置,让 Swagger UI 自动出现一个 Authorize 按钮,登录后自动把 Token 加到请求头里。配置方式有两种:一种是通过 yaml,另一种是通过 OpenAPI Bean。我个人推荐 Bean 方式,因为可以同时把 HTTP 安全方案定义得清晰:
import io.swagger.v3.oas.models.Components; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.security.SecurityRequirement; import io.swagger.v3.oas.models.security.SecurityScheme; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class OpenApiSecurityConfig { private static final String SECURITY_SCHEME_NAME = "bearer-jwt"; @Bean public OpenAPI securityOpenAPI() { return new OpenAPI() .components(new Components() .addSecuritySchemes(SECURITY_SCHEME_NAME, new SecurityScheme() .type(SecurityScheme.Type.HTTP) .scheme("bearer") .bearerFormat("JWT"))) .addSecurityItem(new SecurityRequirement().addList(SECURITY_SCHEME_NAME)); } }配置好之后 Swagger UI 页面右边会多一个 Authorize 按钮。点击填入实际的 JWT 字符串,点 Authorize 就生效,后续所有 Try it out 请求都会自动带上Authorization: Bearer xxx。这个功能极大提高了联调效率,否则每次手动复制 Token 黏到请求头里,调几个接口就烦了。
日常开发还有一个配合方案:让后端临时返回一个测试 Token。我写过一个小接口,在内网环境给开发人员签发 12 小时有效的 JWT,配上这个 Authorize 功能,前后端联调时基本不再被认证挡住。
4.2 Spring Security权限放行清单
如果你的项目引入了 Spring Security,那么 Swagger 的路径必须显式放行,否则文档页面会被认证拦截。Spring Security 6 里的写法已经换成了authorizeHttpRequests+requestMatchers,不再支持老的antMatchers。
package com.example.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; @Configuration @EnableWebSecurity public class SecurityConfig { @Bean public org.springframework.security.web.SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.authorizeHttpRequests(auth -> auth .requestMatchers( "/v3/api-docs/**", "/swagger-ui/**", "/swagger-ui.html", "/webjars/**", "/swagger-resources/**" ).permitAll() .anyRequest().authenticated()) .csrf(csrf -> csrf.disable()); return http.build(); } }需要注意几个点。/swagger-ui/index.html会被/swagger-ui/**覆盖到,所以不需要单列。/v3/api-docs/**要放行,因为 UI 是通过这个端点拉取接口数据的,只放行 UI 不放行 api-docs,页面会一直转圈但列不出接口。
还有一部分项目会开启csrf防止跨站请求。这个对纯 API 项目一般建议关闭,否则 Swagger UI 的 Try it out 发起 POST 请求会被 CSRF 拦截,表现就是明明 Token 正确但提交一直失败。另外如果你是前后端分离部署,还要额外配置 CORS,或者在网关层统一处理跨域。典型症状是浏览器控制台报CORS error,接口全部请求不到。
4.3 多模块文档如何在一个页面聚合
微服务架构下,接口可能分散在多个服务里。每个服务自己有一个 Swagger UI 页面,前端对接时要在多个页面之间来回切换,很不方便。springdoc 支持在网关层做文档聚合,也就是网关暴露一个统一的 Swagger UI,自动把多个服务的接口文档拼到一个列表里。
网关项目里引入相同的 springdoc starter,然后配置:
springdoc: swagger-ui: urls: - name: 用户服务 url: /user-service/v3/api-docs - name: 订单服务 url: /order-service/v3/api-docs - name: 商品服务 url: /product-service/v3/api-docs网关需要在路由层把带/user-service/**的前缀转发到用户服务对应的实例,同时保证/user-service/v3/api-docs能正确代理到目标服务的context-path。如果服务自身没有做前缀剥离,网关转发时要注意 StripPrefix 的配置方式。我在实际项目里踩过一次:网关把/user-service/v3/api-docs转发给用户服务时,服务收到的还是/user-service/v3/api-docs,Controller 路径匹配不上,文档直接 404。后来把转发路径的前缀去掉才恢复正常。
聚合以后,前端打开网关的/swagger-ui/index.html,左上角下拉框里就能看到所有服务,直接在同一个页面调试不同服务的接口。这对小团队来说基本等于白捡了半个 API 网关平台。
5. 生产环境开关与常用配置清单
5.1 按环境自动启停文档
线上环境默认关闭接口文档属于基本素养。springdoc 支持用配置项直接关闭文档入口,最粗暴的开关是springdoc.api-docs.enabled和springdoc.swagger-ui.enabled:
springdoc: api-docs: enabled: false swagger-ui: enabled: falseapi-docs关闭后/v3/api-docs直接 404,swagger-ui关闭后页面也访问不了。生产环境建议两个都关,只留一个有可能被绕过去,虽然这里不算高价值攻击面,但少暴露一点是一点。
更规范的做法是按 profile 区分。把所有文档相关配置放到application-dev.yml和application-prod.yml,dev 环境开、prod 环境关,互不干扰。我习惯在application.yml里留默认配置,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还有一种精细做法是用@Profile("dev")限定 OpenAPI 配置类只在开发环境生效,这样在不改任何配置文件的情况下,生产环境连文档信息 Bean 都不会加载。不过这个方案需要额外注意:只要没加载 Bean,即使 api-docs 还开着,文档内容也没有标题描述信息,体验略怪,所以更推荐 yaml 开关为主。
5.2 一份可挂在项目的完整配置模板
把散落的配置汇总成一份可直接粘贴的模板,方便大家实际项目里按需删减:
springdoc: api-docs: enabled: true path: /v3/api-docs swagger-ui: enabled: true path: /swagger-ui.html tags-sorter: alpha operations-sorter: alpha display-request-duration: true try-it-out-enabled: true persist-authorization: true cache: disabled: true group: configs: - group: 用户端 paths-to-match: /api/user/** packages-to-scan: com.example.controller.user逐项解释一下几个我实际用过且觉得好用的配置:
tags-sorter: alpha和operations-sorter: alpha让目录和接口按字母序排列,接口数量上来以后这个排序功能真的救命,不然生成顺序完全取决于 Controller 扫描顺序。display-request-duration: true会在界面上显示请求耗时,排查性能问题时很直觉。persist-authorization则保证你点的 Authorize 登录信息在刷新页面后不清空,开发时不用反复填 Token。springdoc.cache.disabled建议在开发阶段设成 true,改完 Controller 注解后刷新页面就能看到变化,不用重启服务。线上环境根据情况再决定是否恢复缓存。
如果你不喜欢默认的 Swagger UI 样式,国内生态里还有一个常见选择是给 springdoc 套上增强 UI 的 starter。它不用改任何接口注解,只是替换了前端展示层,界面更符合国内开发者的操作习惯。具体 starter 的坐标会根据 openapi 3 规范发布对应 jakarta 版本,引入后 Swagger UI 的地址会换到增强版的路径。这个按团队偏好选即可,不属于必须项。
6. 常见问题排查与避坑实录
6.1 启动报错的几类典型场景
第一类是启动时报Failed to start bean 'documentationPluginsBootstrapper'。这个报错在 SpringBoot3.x 项目里出现,几乎可以断定是老项目的 springfox 依赖没有移除干净。documentationPluginsBootstrapper是 springfox 的装配入口,SpringBoot3 下根本不应该出现。处理方式:全局搜索 pom 和 gradle 构建文件,把 springfox 相关依赖全部排除,确认依赖树里没有io.springfox后重新启动。
第二类是启动时抛出ClassNotFoundException: jakarta.servlet.*或NoClassDefFoundError。这种一般是 JDK 版本和 SpringBoot 版本不匹配,比如拿 JDK8 编译产物在 JDK17 跑,或者依赖里混入了老版本的 servlet API 包。检查java -version和 Maven 编译器配置,确认两边都是 17。
第三类是Failed to determine a suitable driver class之类与数据库相关的报错,跟 Swagger 无关但容易被误判。实际原因往往是项目引入了未配置的数据源依赖,springdoc 只是恰好把整个上下文启动带崩。这种问题先看堆栈最前面的异常,别被后面跟的 springdoc 信息带偏。
6.2 页面能开但接口列表为空的排查思路
Swagger UI 能打开,说明springdoc-swagger-ui和静态资源映射正常,但左侧没有接口,问题通常出在扫描范围。springdoc 默认扫描启动类所在包及子包,如果你的 Controller 不在这个包结构里,就扫不到。
解决方案是显式配置扫描包:
springdoc: packages-to-scan: com.example.controller还有一种情况是接口列表已生成但是v3/api-docs返回数据里接口数量少。这时候把@Tag注解和@Operation注解检查一遍,注意@Tag的name属性不能重复,如果两个 Controller 用了同一个 name,Swagger UI 有可能只渲染第一个,第二个就被吞了。给每个 Controller 设置独立的name是成本最低的处理办法。
如果页面显示接口但点开某个接口只有路径没有参数说明,多半是方法参数上没有加任何 OpenAPI 注解,写@Parameter或在实体字段写@Schema就会有。
6.3 SpringBoot3中路径匹配配置项已经变了
这条单独拎出来说,是因为我见过太多人把 SpringBoot2.6 时代的兼容方案抄到 3.x 里,然后启动报错:
spring: mvc: pathmatch: matching-strategy: ant_path_matcher这个配置在 SpringBoot2.6 和 2.7 时代是解决 Springfox 兼容问题的常见手段,但 SpringBoot3 已经彻底移除了spring.mvc.pathmatch.matching-strategy属性。如果你在 SpringBoot3 项目里面写上这段,启动时直接抛IllegalArgumentException: Unable to parse property: spring.mvc.pathmatch.matching-strategy或者Unknown property,反而不是 Swagger 的问题。
正确做法是:SpringBoot3 项目不要写这个配置。springdoc-openapi 原生适配PathPatternParser,不需要降级匹配策略。不要用老笔记硬套新版本,这是每年 SpringBoot 大版本升级踩坑的第一大来源。
其他偶发问题我再补几个。如果你设置了server.servlet.context-path,Swagger 的访问路径要带上 context-path。如果你的应用部署在反向代理后面,swagger-ui拉取/v3/api-docs用的地址可能是内网地址,需要在反向代理层配置X-Forwarded-Prefix相关的头,或者手动指定外部访问前缀。再有就是@ParameterObject注解。对于 GET 请求绑定的查询对象,比如分页查询参数对象,如果你希望 Swagger UI 把对象字段展开成一个一个参数输入框而不是折叠成一个 JSON 输入框,给参数加@ParameterObject即可。
最后分享一个小技巧。如果项目被审计要求必须提供离线接口文档,你可以在任何环境启动应用后访问/v3/api-docs保存 JSON 文件,然后用 OpenAPI 生态里的各类离线工具把它渲染成 HTML 或 PDF。这样即使生产环境关了 Swagger UI,审计团队仍然能拿到一份结构完整的接口清单。整个 SpringBoot3.x 整合 Swagger 的链路不算复杂,核心思路就是:老方案别恋战,springdoc 是正路,配置按需加,生产记得关。希望这份实操记录能帮有同样困惑的同学少走几个弯路。