
SpringBoot 3.x 整合 Swagger 是团队升级到 Boot 3 后逃不掉的必修课。太多人还在沿用 Springfox 2.9.2结果在 Spring Boot 2.6 时代就开始白屏升到 Spring Boot 3.x 后更是直接启动失败。这里面的根源在于 Spring Boot 3 完成了从 javax 到 jakarta 的命名空间迁移还把 MVC 路径匹配策略换成了 PathPatternParser老一代集成库已经跟不上节奏。这篇文章会围绕 SpringBoot 3.x 整合 Swagger 的完整过程展开从为什么放弃 Springfox、怎么用 springdoc-openapi 快速接入到配置分组、定制页面、处理泛型、生产环境安全管控等细节一次性把方案和坑位都交代清楚。适合正在做 Spring Boot 3 升级的团队、刚接触接口文档自动生成的新手以及想把 Swagger 从开发环境安全延伸到测试环境的同学参考。1. 为什么 SpringBoot 3.x 不再用 Springfox而是用 springdoc-openapi很多老项目一提到 Swagger第一反应还是 Springfox因为它在 Spring Boot 2.x 早期确实很好用。但技术选型不能只看记忆里的好用还要看它是否还活着、是否跟得上生态演进。1.1 Springfox 的停滞与 Boot 3 底层变化Springfox 最后一次正式发布 3.0.0 是在 2020 年之后基本进入低维护状态。社区里大量 issue 无人回应很多老用户只能靠 hack 方案续命。而 Spring Boot 本身迭代非常快从 2.6 开始把 Spring MVC 的默认路径匹配策略从 AntPathMatcher 切换成了 PathPatternParserSpringfox 内部大量基于 AntPathMatcher 的扫描逻辑就从“能用”变成“靠运气”。Spring Boot 3.x 的改动更加彻底。整个 javax 命名空间被替换为 jakartaSpringfox 底层依赖的 Swagger 2 模型和相关组件并没有跟上这次变更。如果你的项目强行在 Boot 3.x 里引入 Springfox轻则运行时抛出 NoClassDefFoundError重则文档页面直接 404。网上也有一些通过自定义 Bean 和排除依赖来硬兼容的方案我试过维护成本非常高。团队里只要有人升级了某个公共依赖整套 hack 就可能失效查起来非常痛苦。1.2 springdoc-openapi 为什么是当前主流方案springdoc-openapi 的活跃度和社区反馈都明显好于 Springfox。它原生支持 OpenAPI 3 规范并且针对 Spring Boot 3.x 提供了独立的 starter 模块底层自动装配逻辑跟 Boot 3 的机制完全对齐。实际使用中springdoc-openapi 会扫描 Spring 容器内所有带 Web 注解的接口把它们解析成 OpenAPI 文档同时内置了 Swagger UI 前端资源。你只需要添加依赖启动项目后访问指定路径就能看到接口文档。这里要记住两个 starter 的区别springdoc-openapi-starter-webmvc-ui用于 Spring MVC 项目绝大多数 SpringBoot Web 服务都选这个。springdoc-openapi-starter-webflux-ui用于 WebFlux 响应式项目。如果你的项目用的是 Spring Boot 3.2 或 3.3建议选择 springdoc 2.2.0 及以上版本。更早的 2.0.x 在处理某些类上存在兼容性问题比如 HttpMethod 的序列化方式有变化升级后会出现字段丢失或格式异常。不要小看版本号Spring Boot 3.x 的 minor 版本也会影响 springdoc 的解析逻辑稳妥做法是直接到 Maven 中央仓库确认最新稳定版。2. 基础整合SpringBoot 3.x springdoc-openapi 最小可行配置最小可行配置的目标只有一个打开页面看到接口列表。做到这一步后续再谈定制化才有意义。2.1 添加 Maven 或 Gradle 依赖首先把 pom.xml 里残留的 springfox 依赖清干净尤其是 io.springfox:springfox-boot-starter、io.springfox:springfox-swagger2 这类坐标。然后添加以下依赖dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springdoc/groupId artifactIdspringdoc-openapi-starter-webmvc-ui/artifactId version2.2.0/version /dependency /dependenciesGradle 项目对应配置dependencies { implementation org.springframework.boot:spring-boot-starter-web implementation org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0 }为什么不用 springfox 的包因为 Springfox 的核心数据模型基于 Swagger 2而 springdoc 基于 OpenAPI 3。两者对注解、路径解析、响应类型推断的实现都不一样。混用会导致启动时出现多个 DocumentationPluginsBootstrapper 实例接口文档可能生成两份也可能互相覆盖。2.2 启动验证访问 swagger-ui 和 v3/api-docs依赖添加完后直接启动 Spring Boot 应用。假设服务端口是 8080打开以下地址http://localhost:8080/swagger-ui/index.htmlhttp://localhost:8080/swagger-ui.html后者一般会发生一次跳转最终落到 swagger-ui/index.html。页面顶部默认展示一个请求文档地址的输入框内容自动指向当前服务的 OpenAPI JSON 路径。默认 JSON 路径是http://localhost:8080/v3/api-docs直接访问这个地址可以看到一个较大的 JSON 对象里面包含 openapi 版本号、info、paths、components 等核心信息。这个 JSON 就是 Swagger UI 页面渲染的数据源也可以直接给其他 API 工具使用比如 Postman 或 Apifox 都支持导入 OpenAPI 规范。有一个容易误导新手的细节swagger-ui.html 和 v3/api-docs 是两个不同的端点前者是前端页面后者是后端数据结构。如果你在浏览器里看到的是 JSON 而页面打不开问题在前端静态资源加载如果页面能打开但接口列表为空问题在接口扫描或路径匹配。2.3 用注解让接口文档真正可读依赖跑通之后很多项目拿到的是一个“能看但不好用”的文档。原因很简单Controller 上什么都没写Swagger UI 里只能看到请求路径和参数类型字段含义、是否必填、示例值全部缺失前后端联调仍然要拉群互相问。推荐从一开始就约定团队内的注解规范。一个典型示例如下RestController RequestMapping(/api/users) Tag(name 用户管理, description 用户信息的增删改查接口) public class UserController { GetMapping(/{id}) Operation(summary 根据ID查询用户, description 返回用户基本信息包括昵称、头像和注册时间) public ApiResponseUserVO getUser(PathVariable Long id) { return ApiResponse.success(userService.getById(id)); } PostMapping Operation(summary 新建用户, description 创建用户时需要提交完整用户信息) public ApiResponseUserVO createUser(RequestBody Valid UserCreateRequest request) { return ApiResponse.success(userService.create(request)); } }对应的 VO 字段补充 Schema 注解public class UserVO { Schema(description 用户ID, example 10001) private Long id; Schema(description 昵称, example 张三) private String nickname; }简单说下这几个注解的定位Tag 用于给 Controller 分类定义在类名上。Operation 用于描述接口方法summary 是标题description 是详细说明。Schema 用于描述模型字段在 OpenAPI 文档中对应 components/schemas 下的结构。实际项目里注解规范能直接决定文档质量。如果时间充裕建议为 Schema 的 example 字段填充真实的业务示例值比如手机号、邮箱、状态枚举值。这样下游同学可以直接复制 mock 数据减少沟通成本。3. 高级配置与风格定制基础配置只解决了“有没有文档”的问题高级配置解决的是“文档好不好用”的问题。以下三个配置点是我在项目中反复用到的。3.1 使用 OpenAPI Bean 定制文档头信息默认的 Swagger 页面启动后页面顶部显示的内容非常简陋只有默认的 OpenAPI 3.0 字样。开发环境还好一旦要给测试或前端团队提供统一入口标题、版本、负责人信息就非常关键。通过注册 OpenAPI Bean可以统一管理文档元信息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(devexample.com)) .license(new License() .name(Apache 2.0) .url(https://www.apache.org/licenses/LICENSE-2.0))) .externalDocs(new ExternalDocumentation() .description(项目Wiki) .url(https://wiki.example.com)); } }在执行完这个配置后无论是 Swagger UI 顶部标题还是 /v3/api-docs 返回 JSON 中的 info 字段都会同步展示这些内容。多服务场景下这个信息能帮助使用者快速确认自己打开的是哪个服务的文档避免拿错接口地址。3.2 分组文档按 Controller 拆分多套 API 文档当一个服务内包含多个业务模块时几百个接口全部堆在一页里基本没法用。Springdoc 提供了 GroupedOpenApi可以按包路径或路径前缀拆分文档。看这么一段配置Bean public GroupedOpenApi userApi() { return GroupedOpenApi.builder() .group(用户服务) .pathsToMatch(/api/users/**) .packagesToScan(com.example.user.controller) .build(); } Bean public GroupedOpenApi adminApi() { return GroupedOpenApi.builder() .group(管理后台) .pathsToMatch(/api/admin/**) .packagesToScan(com.example.admin.controller) .build(); }配置生效后Swagger UI 右上角会出现一个下拉框可以在“用户服务”和“管理后台”之间切换。切换时页面会重新加载对应分组的 OpenAPI JSON只展示该分组内的接口。分组策略上我建议按业务域分而不是按技术层分。比如把“用户服务”“订单服务”“支付服务”分开比按“普通接口”“管理接口”分开更适合前端和测试使用。另外如果项目用了 Spring Cloud Gateway 聚合多个下游服务文档每个下游服务设计一个独立分组会让网关文档中心更加清爽。3.3 自定义认证插件让 Swagger UI 直接调试鉴权接口很多接口需要登录后才能访问而 Swagger UI 自带的 Authorize 按钮可以被用来管理 Token。配置它的方式是在 OpenAPI 组件中定义安全方案。Bean public OpenAPI customOpenAPI() { final String securitySchemeName bearerAuth; return new OpenAPI() .info(new Info().title(用户中心 API).version(v1.0.0)) .components(new Components() .addSecuritySchemes(securitySchemeName, new SecurityScheme() .name(securitySchemeName) .type(SecurityScheme.Type.HTTP) .scheme(bearer) .bearerFormat(JWT))) .addSecurityItem(new SecurityRequirement().addList(securitySchemeName)); }配置完以后Swagger UI 右上角会出现 Authorize 按钮。点击后输入 Token后续在页面里调试任何接口时请求头都会自动带上 Authorization: Bearer xxx。这对联调流程的提升非常明显省去了“把 Token 手动粘贴进 Header”这种重复劳动。如果你的系统使用 OAuth2也可以基于同样的思路配置 OAuth2 授权流程。字段会比 Bearer Token 复杂包括 authorizationUrl、tokenUrl、scopes 等但底层原理一致。4. 集成过程中的常见坑与排查实录这部分内容是真正值钱的干货。我在处理 SpringBoot 3.x 整合 Swagger 时遇到过的坑基本都集中在这几类。4.1 访问 /swagger-ui.html 时出现 404 或路径找不到大多数情况下这个问题都出在依赖不完整或路径被自定义配置干扰。第一步用 Maven 命令检查依赖树mvn dependency:tree -Dincludesorg.springdoc:springdoc-openapi-starter-webmvc-ui如果输出里没有 springdoc 相关依赖说明 pom.xml 没生效或引入位置不对。如果输出里出现了 springfox 相关包说明有其他公共模块间接引入了旧依赖。这时候需要找到那个模块并排除旧库。很多公司内部的公共 starter 里会带上 springfox升级时特别容易漏。第二步检查配置文件里是否还残留 spring.mvc.pathmatch.matching-strategy。如果你从旧项目迁移过来可能顺手配置了 ant_path_matcher。在 Spring Boot 3.x 中这个配置项虽然还能用但会影响 Springdoc 对 /swagger-ui/** 的路径解析。建议删掉让 Boot 3 使用默认的 PathPatternParser。4.2 依赖冲突与 NoSuchMethodError这类问题比 404 更隐蔽因为编译期能通过运行期才炸。常见报错形式是某个类找不到 Jackson 的某个方法或者某个 Swagger 类里的构造方法参数不匹配。排查分三步走使用 mvn dependency:tree 检查依赖树搜索 io.springfox 相关坐标。检查项目是否有手动放入 lib 目录的 jar 包。全局搜索代码中 import springfox 的地方逐一替换为 io.swagger.v3.oas.annotations 下的包。代码迁移时常用映射关系如下springfox.documentation.builders.RequestHandlerSelectors 不需要保留springdoc 默认扫描。io.swagger.annotations.Api 替换为 io.swagger.v3.oas.annotations.tags.Tag。io.swagger.annotations.ApiOperation 替换为 io.swagger.v3.oas.annotations.Operation。io.swagger.annotations.ApiModelProperty 替换为 io.swagger.v3.oas.annotations.media.Schema。这里有个建议不要试图保留旧注解来做兼容。旧注解曾经为 Swagger 2 设计对 OpenAPI 3 的新特性支持有限。你可能会在某个版本里发现 ApiImplicitParams 能正常工作但换一个 Context 后又失效。一次性迁移干净后续维护成本低很多。4.3 泛型返回类型被绑定或字段丢失Spring Boot 项目基本都会有一个统一返回体比如 ApiResponse 。默认情况下Springdoc 解析泛型时可能出现两种情况一种是只展示 ApiResponse 本身固定的字段T 的具体内容没有展开另一种是泛型嵌套过深文档里出现一堆奇怪的类型变量。常规解决办法是给模型类补充 Schema 注解public class ApiResponseT { Schema(description 状态码, example 200) private Integer code; Schema(description 提示信息, example success) private String message; Schema(description 业务数据) private T data; }对于嵌套比较深的情况比如 ApiResponsePageResult 可以在接口方法上手动指定响应模型ApiResponse(responseCode 200, description 成功, content Content(schema Schema(implementation OrderVO.class)))这样生成的文档中响应模型会直接以 OrderVO 为准而不是被绑定的外层包装类。4.4 Swagger UI 能打开但接口列表为空页面能正常渲染但显示 No operations defined in spec!这类问题可以从两个方向排查。第一Controller 是否被扫描到。如果你的 Controller 位于主启动类所在包的子包之外Spring Boot 默认扫描不到。需要修改启动类配置SpringBootApplication(scanBasePackages com.example) public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }第二是否配置了 GroupedOpenApi 但包名或路径匹配写错。包名写错时不会启动报错只是不匹配任何 Controller接口列表自然为空。可以通过开启 debug 日志来观察扫描结果springdoc: api-docs: enabled: true swagger-ui: enabled: true logging: level: org.springdoc: DEBUG启动后观察日志中出现的 Controller 列表能快速定位是扫描问题还是路径过滤问题。5. 接口文档的安全防护与生产环境实践Swagger UI 是一个非常好用的调试工具但它同时也是一个信息暴露面。很多扫描器会优先探测 /swagger-ui.html、/v3/api-docs、/v2/api-docs 这些路径。一旦被外部访问到接口路由、字段结构、服务模块划分这些内部信息就会一览无余。所以生产环境绝不能裸奔开放文档。这不是教大家怎么利用漏洞而是提醒如何做好边界管控。5.1 通过配置开关控制文档是否启用最简单直接的方式是在生产环境关闭文档端点springdoc: api-docs: enabled: false swagger-ui: enabled: false将这段配置放到 application-prod.yml 中生产环境启动后/v3/api-docs 和 /swagger-ui.* 均不会注册。这样即使被扫描路径服务也会直接返回 404。如果你希望默认关闭、只在显式配置时打开可以结合条件注解Bean ConditionalOnProperty(name app.swagger.enabled, havingValue true, matchIfMissing false) public OpenAPI customOpenAPI() { return new OpenAPI().info(new Info().title(Internal API)); }matchIfMissingfalse 表示即便没有配置该属性也不会生成 OpenAPI Bean。这样就算配置文件漏写开关也不会出现文档意外暴露的情况。5.2 使用 Spring Security 限制访问范围如果生产环境必须允许部分人员访问接口文档比如给运维或测试同学调试用推荐引入 Spring Security 做路径鉴权。一个简单的示例配置思路http.authorizeHttpRequests(auth - auth .requestMatchers(/swagger-ui/**, /swagger-ui.html, /v3/api-docs/**) .hasRole(ADMIN) .anyRequest().permitAll());这样只有具备 ADMIN 角色的用户才能访问文档相关路径其他用户不可见。如果你使用网关统一鉴权也可以在网关层把这类请求路由到内网环境外部流量一律不转发。5.3 在反向代理层屏蔽文档路径在 Nginx 这类反向代理中可以提前屏蔽location ~ ^/(swagger-ui|v3/api-docs) { deny all; return 403; }这里的原则是默认关闭按需开放。即便后端配置遗漏开关网关层也能挡住大部分风险。对任何包含大量业务字段信息的接口文档公开到公网都不是好主意尽量保持在可信网络内访问。5.4 落地扩展建议结合我在真实项目里的经验这里整理几条 checklist供团队参考将 Swagger 配置抽成独立配置类避免散落在业务代码里。环境相关的开关一律放入 profile 配置文件保持配置可追踪。对接外部团队时优先提供内网文档入口不要图省事直接公网开放。定期检查文档是否与实际接口一致废弃接口及时清出 Tag 和 Operation。接口变更在提交时顺便更新文档描述避免测试和前端基于过期文档联调。我在实际集成 SpringBoot 3.x 和 Swagger 的过程中最强烈的感受是升级并不可怕可怕的是老依赖没清理干净。你以为换一个 starter 就算集成完成实际上大量历史包袱会在运行时悄悄拉低稳定性。如果你正卡在升级过程中先检查依赖树清掉 springfox引入 springdoc-openapi再对照这篇文章排查访问路径。最后再强调一次生产环境的 Swagger 默认关闭内网按需开放这是最省心的安全策略。文档是工具不是门面管好边界才能让工具真正服务开发和协作。