SpringBoot 4.0.3配置Swagger:SpringDoc实践指南 搞Java后端的这两年我在不同项目里前前后后配过不下十次Swagger。从早期的Springfox到后来的SpringDoc再到Spring Boot 4.0.3这种新版本上重新梳理依赖踩过的坑确实能写成一箩筐。今天就拿“SpringBoot 4.0.3配置Swagger”这个具体场景把我最终落地的配置和完整思路分享出来给同样在升级路上卡壳的朋友指个路。如果你刚从旧版本升上来最直观的感受应该是老一套配置全都不好使了。Springfox那套东西在3.x时代就已经问题不断到了4.0.3这种基于Jakarta生态的新版本上更是直接翻车启动报错、扫描不到Controller、UI页面404各种怪问题轮番上阵。但这篇文章不是来劝你放弃Swagger的而是告诉你在新版本下该怎么用更符合规范的SpringDoc方案把接口文档这件事做得更干净、更可控。全文会从版本选型、基础配置、分组鉴权、生产开关再到高频问题排查一条线捋下来。看的过程中你可以直接照着复制改也可以先看一遍原理再动手这样出了问题也更容易定位。1. 版本选型为什么Spring Boot 4.0.3必须放弃Springfox1.1 先理清Swagger、Springfox和SpringDoc的关系不少人对“Swagger”这个词有误解以为它是一个具体的库。实际上Swagger最早是一套API工具链后来演变成了OpenAPI规范。我们平时说的“配置Swagger”通常指的是给项目接入一套能自动生成接口文档的组件把Controller里的接口、参数、返回值展示成网页还能在线调试。历史上最常用的集成方式是Springfox它提供了springfox-swagger2和springfox-swagger-ui两个核心依赖Swagger注解也用的io.swagger.annotations包。这套方案在Spring Boot 2.x时代用得很顺手但到了Spring Boot 3.x之后Springfox基本就跟不上了。Spring Boot 4.0.3属于新一代版本底层对应的是较新的Spring Framework 6.x/7.x体系和Jakarta命名空间。Springfox 3.0.0内置了大量javax.*依赖并且内部强依赖了Spring 5的老版路径匹配策略在4.0.3上启动时会报类似Failed to start bean documentationPluginsBootstrapper的经典错误。这不是你配置写错了而是这套库本身已经停更跟新框架不兼容。1.2 SpringDoc才是新版本下的标准选择SpringDoc是社区里真正跟上了Spring Boot新版本的解决方案。它直接实现了OpenAPI 3规范依赖采用org.springdoc:springdoc-openapi-starter-webmvc-ui注解也换成了io.swagger.v3.oas.annotation.*包比如Operation、Parameter、Schema。用SpringDoc之后Swagger UI页面变成了/swagger-ui/index.htmlOpenAPI的JSON接口地址是/v3/api-docs。这两个路径跟老Springfox完全不一样很多刚从2.x升上来的同学习惯性访问/swagger-ui.html或/v2/api-docs自然看到404。我做过一个简单的对比方便你理解为什么SpringBoot 4.0.3场景下推荐SpringDoc对比维度SpringfoxSpringDoc维护状态已长期停更社区活跃持续跟进新版本依赖命名空间javaxjakarta规范支持Swagger 2.0 / OpenAPI 2OpenAPI 3注解包io.swagger.annotationsio.swagger.v3.oas.annotation兼容Spring Boot 3极差良好自带UI路径/swagger-ui.html/swagger-ui/index.html分组能力支持但配置繁琐原生支持配置简单选择一个技术方案最忌讳“因为它老所以它稳”。Springfox在Spring Boot 4.0.3这个场景下已经不是稳不稳的问题而是能不能启动的问题。所以选型这件事没有太多悬念后面所有内容都基于SpringDoc展开。2. 从零搭建依赖引入与基础配置2.1 POM依赖里必须注意的细节新建一个Spring Boot 4.0.3项目之后第一步就是往pom.xml里加依赖。这里的坑在于网上大量旧教程会让你加springfox-swagger2千万别再抄了。正确做法是加SpringDoc的starter依赖dependency groupIdorg.springdoc/groupId artifactIdspringdoc-openapi-starter-webmvc-ui/artifactId version2.8.6/version /dependency版本号用2.8.x以上比较稳妥这个系列就是专门适配新版本Spring Boot的。如果你的项目里还保留着老Springfox依赖建议全部移除干净否则会出现两个文档框架抢路径的情况表现就是端口冲突、Bean加载异常、页面样式丢失。另外如果你的项目是WebFlux响应式架构不要用webmvc-ui这个依赖要换springdoc-openapi-starter-webflux-ui这是很多人忽略的地方。判断标准很简单你在pom.xml里引入的是spring-boot-starter-web还是spring-boot-starter-webflux。2.2 application.yml核心配置项SpringDoc大部分场景下不开任何配置就能跑起来但实际项目中我通常会配好下面这些项避免后续产生奇奇怪怪的小问题springdoc: api-docs: path: /v3/api-docs enabled: true swagger-ui: path: /swagger-ui/index.html enabled: true tags-sorter: alpha operations-sorter: alpha try-it-out-enabled: true persist-authorization: true group-configs: - group: default packages-to-scan: com.example.demo.controller这几个配置项的含义分别是api-docs.pathOpenAPI JSON的访问路径默认就是/v3/api-docs一般不用改但如果你有网关聚合需求可能会改成带服务名的路径。swagger-ui.path页面路径同样默认就行。tags-sorter和operations-sorter让文档按字母顺序排列接口多的时候重要性就体现出来了。operations-sorter设为method也可以按请求方法排序看个人习惯。try-it-out-enabled开启在线调试按钮。persist-authorization刷新页面后保留已经填写的认证信息调试Bearer Token时能少粘几次。2.3 一个最小可运行的Controller示例配置完成后写一个非常普通的Controller就能验证效果RestController RequestMapping(/api/user) public class UserController { GetMapping(/{id}) Operation(summary 查询用户信息, description 根据ID返回用户基本信息) public UserVO getUser(PathVariable Parameter(description 用户ID) Long id) { UserVO vo new UserVO(); vo.setId(id); vo.setName(测试用户); return vo; } PostMapping Operation(summary 新增用户) public UserVO createUser(RequestBody Valid UserCreateDTO dto) { return new UserVO(); } }这里用Operation替代了老版的Api用Parameter替代了ApiParam。如果项目里还有老代码没迁移也尽量统一成v3注解因为SpringDoc对老注解的兼容支持仅限于基础场景复杂一点的参数说明可能就不生效了。启动项目后访问http://localhost:8080/swagger-ui/index.html正常会看到一个挺清爽的页面左侧列出所有接口分组点开接口可以看到请求参数、响应结构下面可以直接点Try it out发起在线请求。再访问http://localhost:8080/v3/api-docs会得到一段JSON格式的OpenAPI定义这就是页面背后渲染的数据源。3. 让文档更好用分组、鉴权与生产环境控制3.1 多模块按包分组解决接口混在一起的问题单个服务模块越大接口越多所有接口都摊在一个页面上会很难受。SpringDoc原生支持分组一个经典做法是按Controller所在包来分组比如一个服务同时提供用户端和管理端接口就可以拆成两个组springdoc: group-configs: - group: app packages-to-scan: com.example.demo.controller.app - group: admin packages-to-scan: com.example.demo.controller.admin配置完后UI页面的左上角会出现分组下拉框选择不同组只看对应接口。这比在一个文档里翻几百个接口用户体验好太多了。也可以用paths-to-match来控制哪些URL路径属于哪个组比如springdoc: group-configs: - group: api paths-to-match: /api/** - group: inner paths-to-match: /inner/**如果你的包路径和URL路径都有一定规则组合使用两个条件也是可以的。SpringDoc在判断归属时会取交集还是并集这个不同版本行为略有差异但绝大多数场景下用单一条件就够了。我实际项目里习惯用包扫描因为代码结构往往比URL路径更稳定。再补充一个动态分组的方法。如果分组条件需要运行时计算可以在Java代码里注册多个GroupedOpenApi的BeanConfiguration public class OpenApiConfig { Bean public GroupedOpenApi appApi() { return GroupedOpenApi.builder() .group(app) .packagesToScan(com.example.demo.controller.app) .build(); } Bean public GroupedOpenApi adminApi() { return GroupedOpenApi.builder() .group(admin) .pathsToMatch(/admin/**) .build(); } }这种方式的灵活性更高比如你想在同一组里过滤掉某些特定接口可以用pathsToExclude排除。3.2 带Token鉴权的接口如何在文档中传递认证信息很多项目接口都加了登录鉴权通常是用Header里的Authorization: Bearer token。SpringDoc生成的文档默认不知道这回事你在页面上点哪个接口都发不出带Token的请求。要让Swagger UI支持全局认证只需要在启动类或配置类里加一个OpenAPI的BeanBean public OpenAPI customOpenAPI() { return new OpenAPI() .components(new Components() .addSecuritySchemes(bearer, new SecurityScheme() .type(SecurityScheme.Type.HTTP) .scheme(bearer) .bearerFormat(JWT))) .info(new Info() .title(用户服务API文档) .version(1.0.0) .description(用户服务对外接口说明)); }这里声明了一个HTTP Bearer方案。保存后Swagger页面右上角会出现一个Authorize按钮点开后输入Bearer eyJhbGciOi...这种完整Token后续所有在线调试请求都会自动带上认证头。需要注意的是bearerFormat(JWT)只是提示让UI展示“该Token是JWT格式”它并不会检查Token内容。真正拦截未认证请求的还是你项目里的Spring Security或自定义拦截器两者各司其职。如果你还同时使用了Spring Security千万别漏掉文档路径的放行配置否则/v3/api-docs和/swagger-ui/**会被拦下来页面直接给你弹一个登录框。在Spring Security配置里加上.authorizeHttpRequests(auth - auth .requestMatchers(/swagger-ui/**, /v3/api-docs/**).permitAll() .anyRequest().authenticated())这里有个小细节Spring Security的版本不同requestMatchers的写法会有差异新版已经不支持antMatchers了所以我用新API写法。不能懒省事直接permitAll()那等于把你所有业务接口都暴露了页面上能看到也能调就失去文档保护的意义了。3.3 生产环境优雅关闭文档而不是删除依赖接口文档放在开发环境没问题但生产环境一般要关掉。最傻瓜的方法是把依赖删掉但这样会导致/v3/api-docs路径没了如果你还需要在网关层面做接口聚合就得保留依赖、只关页面。我推荐统一用配置来控制springdoc: api-docs: enabled: false swagger-ui: enabled: false生产环境把这两个开关设为false文档页面和JSON接口就都不可访问了。如果只想关闭Web页面但保留JSON用于内部链路可以只把swagger-ui.enabled设成false。当然更严格的做法是在网关层面直接禁止访问这些路径配置读到环境变量来自动切换这一层是部署策略问题。有人问过要不要按环境写死我的建议是用application-prod.yml单独指定就行不同环境继承不同配置文件比在代码里判断environment强多了。3.4 Knife4j增强UI值不值得用很多项目会额外接Knife4j来做UI增强比原生Swagger UI好看不少还能一键调试、导出Markdown文档。但有一点必须提醒Knife4j和SpringDoc版本的匹配关系很敏感如果用的是Spring Boot 4.0.3搭配新版SpringDoc务必要选对应的Knife4j版本。方案对不上时页面会白屏而且报错信息不一定直观。我不建议把Knife4j当成“必须项”。原生Swagger UI在最新版本里已经足够好用了它对OpenAPI 3支持也比较完整。Knife4j主要赢在中文界面和文档导出如果团队习惯在页面里直接浏览接口文档且对颜值有要求可以体验一下。但引入第三方UI的同时也引入了一层兼容性风险生产项目里要慎重评估。4. 常见问题排查实录4.1 页面404为什么访问swagger-ui是空白或失败这个问题在Spring Boot新旧版本升级后最常出现。先澄清一下新版SpringDoc的UI地址是/swagger-ui/index.html老版本用的/swagger-ui.html已经不适用了。你输入http://localhost:8080/swagger-ui.html看到404太正常了。其次是路径冲突。Swagger UI其实是一堆静态资源如果项目里注册了自己定义的WebMvcConfigurer并覆盖了资源路径可能会把/swagger-ui/**也吞掉。排查时可以先把自定义资源映射全部注释掉再试确认是这个问题后再改成保留白名单。再有一种可能就是Spring Security没有放行但没有弹登录框而是直接404经常会让排查跑偏。用浏览器F12看Network响应状态码比纯看页面更可靠如果返回302跳到登录页基本就是安全配置问题。4.2 启动报错文档相关Bean初始化失败如果你在Spring Boot 4.0.3里还能见到大量javax相关的ClassNotFound、documentationPluginsBootstrapper报错基本就说明项目里还残留着老版本Springfox的传递依赖。用mvn dependency:tree查一下把所有io.springfox:springfox-*依赖全找出来移除。我还遇到过一种情况项目引入了某个内部基础组件包里面间接依赖了老Springfox。这种挺难查的因为不是直接引入在POM里。解决办法就是一层层追踪依赖树确定是哪个构件引入的然后在该依赖上做全局排除。dependency groupIdcom.example.common/groupId artifactIdcommon-base/artifactId exclusions exclusion groupIdio.springfox/groupId artifactIdspringfox-swagger2/artifactId /exclusion /exclusions /dependency4.3 注解写了不生效文档里没有描述信息老项目的Controller上肯定满天飞着Api、ApiOperation、ApiParam这些注解。如果把这些代码原封不动搬到新项目里文档大概率能出来但描述信息十有八九丢了。原因就是SpringDoc原生支持的是io.swagger.v3.oas.annotations包下的Operation、Parameter、Schema。迁移工作虽然琐碎但值得做。一个快速替换思路是老注解新注解ApiTagApiOperationOperationApiParamParameterApiModelSchemaApiModelPropertySchema(description ...)ApiIgnoreHidden替换完再重启文档里的摘要和参数描述就都能显示了。这里容易漏的是DTO字段上的ApiModelProperty换成Schema后记得把required等属性也带过去。4.4 参数对象里的泛型类型丢了响应示例显示不全SpringDoc能自动解析返回类型里的泛型比如ResultUserVO会正确展示外层包装和内部数据结构。但如果你把泛型的类型做得太复杂比如两层嵌套加MapString, ListUserVO有时UI显示会不完整。此时可以在方法上显式声明Operation(summary 批量查询) public ResultListUserVO batchQuery(RequestBody BatchQueryDTO dto) {SpringDoc在解析ListUserVO时从方法签名里能拿到完整泛型信息只要Controller方法有显式的返回类型一般不会出问题。如果你的代码习惯是返回ResponseEntity?或者Object那文档确实无能为力它拿不到运行时类型。这种建议改方法签名而不是想着怎么让框架去猜。4.5 路径匹配异常Spring MVC的路径策略和文档不匹配早期Spring Boot 2.x时代很多人从Springfox迁移到SpringDoc时会碰到spring.mvc.pathmatch.matching-strategy相关的异常网上常见解决办法是设置成ANT_PATH_MATCHER。这个老套路在新版本里有副作用Spring Boot 3.x之后Spring MVC默认已经是PathPatternParser而SpringDoc在新版本身已经兼容了这种策略不需要再去改matching-strategy。如果你的项目里还有老代码会把matching-strategy显式改成ant_path_matcher反而可能引发其他路由匹配问题。建议删除这个配置重启后再看文档是否正常渲染。如果仍然有路径匹配报错重点检查项目里是否存在多个HandlerMapping比如额外加了Interceptor或自定义HandlerMapping导致SpringDoc扫描不到正确信息。4.6 排错速查表现象大概率原因处理方式swagger-ui.html 404使用了老路径改用/swagger-ui/index.html页面无样式/空白静态资源被拦截检查资源映射和Security放行启动注册Bean失败残留旧Springfox依赖dependency:tree排查并排除描述信息为空还在用io.swagger老注解迁移到io.swagger.v3注解泛型显示不全方法返回Object或?改为显式返回类型文档里看不到某接口路径或包扫描范围不对检查group-configs配置生产环境也能访问文档没关enabled开关配置api-docs.enabledfalse和swagger-ui.enabledfalse5. 写文档的一些建议以及Swagger之外的那些事5.1 注释信息要规范把文档当成产品来写写接口的时候很多人随手在Operation里写一句“查询”就算完事结果文档页面上信息量极低。我以前也这样后来发现这套自动生成的文档是前后端协作最重要的依据写清楚和不写清楚联调效率差很多倍。我现在坚持的做法比较简单Operation的summary用一句话说清接口能做什么description写清前置条件和特殊业务逻辑参数上用Parameter(description 用户ID必填)标明含义和约束响应对象上补Schema(description 用户返回对象)每个字段都写上业务含义、示例值和是否必填。一个典型示例Schema(description 用户创建请求) public class UserCreateDTO { Schema(description 用户姓名1-20个字符, example 张三, required true) NotBlank Size(max 20) private String name; Schema(description 手机号11位数字, example 13800138000) Pattern(regexp ^1[3-9]\\d{9}$) private String phone; }这样做的价值不是形式上的好看而是让接口使用方不需要翻代码、翻需求文档就能理解每个字段。尤其是字段校验规则写在代码里但别人看不到写明在Schema里反而更直观。5.2 用全局OpenAPI配置统一文档头信息多个服务如果都有自己的文档建议把info信息统一维护。除了标题、版本号还可以在里面放联系人信息让对接方知道接口有问题该找谁。Bean public OpenAPI customOpenAPI() { return new OpenAPI() .info(new Info() .title(用户服务 API) .version(v1.0.0) .description(负责用户注册、登录、资料查询相关接口) .contact(new Contact() .name(后端接口维护组) .email(backendexample.com))) .addSecurityItem(new SecurityRequirement().addList(bearer)); }这里还包含了addSecurityItem它表示默认所有接口都要求携带Bearer认证。如果你有少数接口不需要认证可以在Controller方法上加Operation的security属性覆盖成一个空数组比如Operation(security {})这样文档页面上会提示该接口无需认证。5.3 网关层聚合多服务文档的思路微服务场景下每个服务都开一个独立的Swagger页面很麻烦典型的做法是在网关层聚合。SpringDoc官方提供了springdoc-openapi-starter-webmvc-api和网关聚合方案但我实际项目里更常用的还是在前端放一个静态导航页把不同服务的UI地址汇总在一起点击跳转。如果你确实想在网关层统一拉取JSON再组装成一个页面复杂度会比想象中高因为不同服务的OpenAPI JSON中paths、components都要做合并。这个合并过程涉及大量重复对象去重的细节不是简单拼接JSON就完事。所以我的建议是先通过导航页解决访问入口的问题等确实需要多服务一份完整文档时再投入精力搞聚合否则很容易一地鸡毛。5.4 性能与安全容易被忽略但值得关注有人把Swagger文档丢在开发服务器上觉得内网访问没关系。但接口文档本质上暴露了你的接口结构、字段含义甚至内部业务逻辑有些服务还通过Swagger页面直接在线调用接口。如果服务器暴露公网而文档又没有加访问控制就相当于把系统内部结构晒在公网上。我一般做两道防护一道是Spring Security对/swagger-ui/**和/v3/api-docs/**做认证另一道是网关或接入层限制文档端口只在测试环境可访问。开启SpringDoc本身对接口性能影响其实很小主要开销在于首次启动时扫描和生成OpenAPI JSON。但接口特别多时每次访问/v3/api-docs会重新序列化JSON如果把它暴露在频繁被拉取的服务上也会产生不必要的CPU消耗。这个场景可以考虑缓存生成的OpenAPI配置或者干脆把页面访问限流。5.5 留一个更现代化的接口管理思路写完SpringDoc的配置我总想多说一句Swagger这套东西虽然普及率高但它本质上是“代码驱动文档”文档内容和代码强绑定有些团队连模型描述都懒得写生成出来的文档自然没什么营养。我们现在很多新项目开始尝试“契约先行”的模式也就是先说清楚接口长什么样再写实现代码。这种模式下OpenAPI文件本身成为唯一的接口事实来源代码根据规范去实现文档不再是代码的衍生品。SpringDoc能输出OpenAPI JSON只是第一步更深层的用法是把这些JSON纳入接口评审、Mock联调、自动化测试的流程里。如果你已经在用Swagger不妨在这个方向上想一步也许能打开一个新局面。最后讲点个人感受。Spring Boot 4.0.3配Swagger这件事真正难的不是写几行配置而是搞清楚框架升级后哪些老经验要作废哪些做法应该调整。Springfox是历史产物不代表它不好但技术选型永远要把后续维护成本算进去。SpringDoc这个方案我用到现在最在意的两点是它跟新版框架跟得紧、配置项清晰这两个优点在长期项目里能省下不少沟通和排查的时间。如果你正卡在升级后的报错里先把手上的旧依赖清理干净再按这篇文章的步骤来一遍大概率就能稳定跑起来。