指南)
SpringBoot 4.0.3配置Swagger——從翻車到順手的完整踩坑記錄先說個真實場景上個月有個老朋友找到我說他們團(tuán)隊剛從SpringBoot 2.7升級到SpringBoot 4.0.3結(jié)果原來好端端的Swagger界面全掛了接口文檔白茫茫一片。他照著網(wǎng)上那些教程反復(fù)排查了兩天什么springfox、springdoc、knife4j全都試了一遍不是啟動報錯就是訪問404整個人快崩潰了。我聽完第一句話就問“你用的springdoc-openapi是什么版本”他愣了一下說“不就是最新版嗎”問題就出在這里。SpringBoot 4.0.3是個大版本躍遷底層框架從javax全面切換到j(luò)akartaSpringfox早就停止維護(hù)了而舊版的springdoc-openapi也不兼容。如果還拿老經(jīng)驗去做新配置翻車是必然的。這篇文章我會從版本兼容性、依賴引入、核心配置、鑒權(quán)集成到常見問題排查把SpringBoot 4.0.3接Swagger這件事掰開揉碎講清楚。尤其是版本對應(yīng)關(guān)系、過濾器放行規(guī)則、分組配置這些坑全是實測踩出來的經(jīng)驗。適合正在做版本升級、或者其他項目要新接文檔服務(wù)的同學(xué)參考直接照著抄就行。1. SpringBoot 4.0.3與Swagger的兼容性真相1.1 版本選錯一切白費先說結(jié)論SpringBoot 4.0.3搭配Swagger現(xiàn)在唯一靠譜的選擇是springdoc-openapi的3.x版本而且必須是某個具體的里程碑版本之后才行。我一開始用的還是2.8.x的老版本啟動直接拋NoSuchMethodError后來翻官方倉庫的release notes才確認(rèn)從SpringBoot 4.0開始Jakarta EE版本已經(jīng)從10升到了11Spring Framework也升級到了7.0整個Servlet API的語義都有變化舊版springdoc-openapi壓根無法感知這些新接口。如果你還想用springfox我勸你趁早死心。這個項目在2020年左右就停更了官網(wǎng)首頁寫著“maintenance mode”它的底層依賴還是springfox-swagger2那套老路徑連OpenAPI 3.0規(guī)范都沒完整支持更別說SpringBoot 4.x這種全新的自動裝配機制。硬上的結(jié)果就是把整個項目拖垮連正常啟動都做不到。我列一張在實際項目中驗證過的對照表SpringBoot版本推薦Swagger方案具體依賴2.xspringfox 3.0.0 或 springdoc 1.x/2.xspringfox-swagger23.xspringdoc 2.xspringdoc-openapi-starter-webmvc-ui4.0.3springdoc 3.xspringdoc-openapi-starter-webmvc-ui4.0.3WebFluxspringdoc 3.xspringdoc-openapi-starter-webflux-ui有個關(guān)鍵點SpringBoot 3.x和4.x用的Maven坐標(biāo)前綴都是springdoc-openapi-starter-*不是老式的springdoc-openapi-ui后者在SpringBoot 3之后就不再生效了我也踩過這個坑換成starter系列后一切正常。1.2 SpringBoot 4.0.3到底變了什么很多人會問SpringBoot 4.0.3不就是3.x的延續(xù)嗎配置Swagger怎么就有這么大差別表面上只是版本號變了其實改動相當(dāng)多。第一個大變化是完整的Jakarta遷移。SpringBoot 4.0.3的所有內(nèi)置組件包括spring-boot-starter-web、內(nèi)嵌Tomcat都基于Jakarta Servlet 6.1來構(gòu)建包名從javax.servlet.*變成了jakarta.servlet.*。如果第三方庫還引用老包名運行時會直接找不到類最典型的表現(xiàn)就是啟動日志里出現(xiàn)ClassNotFoundException: javax.servlet.Filter。Swagger作為涉及Web層的一系列Filter和Servlet自然逃不過這個兼容性問題。第二個大變化是自動配置機制改了。SpringBoot從3.x開始慢慢淘汰spring.factories到4.0.3已經(jīng)完全切換為AutoConfiguration.imports文件機制。Springfox這類老庫還在用老方式注冊自動配置SpringBoot掃描不到自然不會加載Swagger的配置類。第三個變化是默認(rèn)路徑匹配策略。SpringBoot 4.0.3延續(xù)并且強化了PathPatternParser作為默認(rèn)策略替代了老式的AntPathMatcher。這會導(dǎo)致很多通配符寫法失效比如原來只配了/v2/api-docs/**而不配/v3/api-docs/**就會出現(xiàn)明明配置了但訪問不了的問題。理解了這三個底層變化你就能明白為什么網(wǎng)上那些SpringBoot 2.x的教程在4.0.3下完全失效——不是Swagger變了而是整個生態(tài)變了只能用適配新生態(tài)的方案。2. 工程初始化與依賴引入2.1 環(huán)境準(zhǔn)備既然是跟著實操走我先說清楚我這邊的基礎(chǔ)環(huán)境方便對照J(rèn)DK 17以上推薦JDK 21別用JDK 8Maven 3.9以上Gradle 8.x也可以SpringBoot 4.0.3springdoc-openapi 3.x具體子版本見后文JDK版本這塊沒什么好商量的SpringBoot 4.0.3的最低要求就是JDK 17如果你還在JDK 8上跑連項目都啟動不了。好消息是Swagger本身對JDK版本沒有額外限制所以環(huán)境上只要滿足SpringBoot的基本要求就夠了。2.2 Maven依賴的精確寫法Maven項目的pom.xml里需要引入這兩個核心依賴dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springdoc/groupId artifactIdspringdoc-openapi-starter-webmvc-ui/artifactId version3.0.0/version /dependency截止到這篇文章寫作時3.0.0是我實測下來最穩(wěn)定的一個版本它明確聲明支持SpringBoot 4.0.x。如果你用Gradle對應(yīng)寫法是implementation org.springdoc:springdoc-openapi-starter-webmvc-ui:3.0.0有個細(xì)節(jié)需要留個心眼不要順手引入spring-boot-starter-validation之外的東西去“增強”Swagger功能。很多教程會說引入springdoc-openapi-ui或者swagger-annotations但在SpringBoot 4.0.3下這些依賴往往和starter內(nèi)部依賴沖突反而導(dǎo)致/swagger-ui/index.html無法渲染。保持最小依賴集合能跑起來再逐步加?xùn)|西這是我認(rèn)為最省事的路徑。2.3 首次啟動驗證依賴加好之后直接啟動項目正常情況下控制臺會打印一行特征日志o.s.springdoc.webmvc.ui.SwaggerWelcome: swagger-ui processed這說明springdoc已經(jīng)成功注入了Swagger的UI組件。此時直接訪問兩個地址做驗證API文檔JSON地址http://localhost:8080/v3/api-docsSwagger UI頁面地址http://localhost:8080/swagger-ui/index.html如果兩個地址都能正常返回內(nèi)容說明最基礎(chǔ)的鏈路已經(jīng)通了。如果訪問/v3/api-docs返回404優(yōu)先檢查你是不是還在依賴?yán)习姹镜膕pringfox或者類路徑里有沒有歷史遺留的Swagger依賴。我見過好多次項目里同時存在兩個Swagger相關(guān)依賴包沖突導(dǎo)致路由全部失效白白排查了一整天。3. 核心配置與常用注解實操3.1 配置文件里的關(guān)鍵參數(shù)Swagger接好之后不能直接扔著不管生產(chǎn)環(huán)境的文檔需要定制化。SpringBoot 4.0.3下application.yml里最常用的配置段是這樣的springdoc: api-docs: enabled: true path: /v3/api-docs swagger-ui: enabled: true path: /swagger-ui.html display-request-duration: true operations-sorter: method tags-sorter: alpha disable-swagger-default-url: true packages-to-scan: com.example.demo.controller paths-to-match: /api/**,/manage/** show-actuator: false幾個參數(shù)單獨解釋一下packages-to-scan用來指定掃描哪個包下的Controller。如果你有多個模塊可以寫多個包名用逗號分隔。這個參數(shù)的本質(zhì)是限定文檔的展示范圍避免把項目里的監(jiān)控接口、內(nèi)部接口都暴露出來。paths-to-match是做路徑過濾的只匹配以/api/或者/manage/開頭的接口。這兩個參數(shù)組合起來基本可以做到“文檔只看該看的”既白又干凈。operations-sorter: method表示接口按HTTP方法排序GET排在POST前面這樣UI上看起來更整齊。tags-sorter: alpha表示按控制器名稱的字母順序排列多模塊項目里這樣排會更清晰。還有一個比較冷門的參數(shù)display-request-duration開啟后UI會在每個接口旁邊顯示請求耗時這對后續(xù)做接口性能觀測挺有幫助建議開發(fā)環(huán)境直接打開。3.2 Java代碼里的注解實戰(zhàn)依賴和配置文件都搞定后代碼層面只需要補充少量注解Swagger就能把接口文檔生成出來。最常用的三件套是Tag標(biāo)在Controller類上Operation標(biāo)在方法上Parameter標(biāo)在方法參數(shù)上。舉一個實際項目中最常見的寫法Tag(name 用戶管理, description 用戶注冊、登錄、信息查詢等接口) RestController RequestMapping(/api/user) public class UserController { Operation(summary 查詢用戶詳情, description 根據(jù)用戶ID查詢用戶詳細(xì)信息) GetMapping(/{id}) public ResultVOUserVO getUser(Parameter(description 用戶ID) PathVariable Long id) { // 業(yè)務(wù)代碼略 } }這里有個容易忽略的坑如果項目里用了ResultVOT這種統(tǒng)一返回結(jié)構(gòu)springdoc會嘗試把這個泛型結(jié)構(gòu)里的每個字段都展示在文檔里。如果ResultVO里有一個code、一個message、一個data還好但如果你把Throwable異常對象也塞進(jìn)去了文檔會變得極其冗長。解決辦法有兩個要么在DTO里加Schema(hidden true)隱藏不需要的字段要么用ApiResponse手動指定返回結(jié)構(gòu)我一般推薦后者。3.3 Swagger注解與OpenAPI注解的差異這里要給從springfox遷移過來的讀者做個提醒springdoc體系使用的是io.swagger.core.v3的注解比如Tag、Operation、Schema不是以前springfox用的io.swagger.annotations.*。如果你發(fā)現(xiàn)代碼里寫Api、ApiOperation編譯沒問題但頁面怎么也不顯示那說明你引入的還是舊注解需要全部替換。對照關(guān)系可以參考這張表舊寫法springfox新寫法springdocApiTagApiOperationOperationApiParamParameterApiModelSchemaApiModelPropertySchema(description xxx)ApiIgnoreHidden從數(shù)量上看新注解的語義更清晰參數(shù)也更簡潔但如果你寫習(xí)慣了老注解剛切換時會有些別扭。我的建議是直接改不要做兼容層因為springdoc本身就不認(rèn)老注解留著只會造成文檔信息缺失。3.4 分組配置的進(jìn)階玩法如果你的項目是微服務(wù)架構(gòu)一個SpringBoot應(yīng)用對外暴露很多模塊接口不想讓所有接口混在一個文檔里可以用GroupedOpenApi做分組。這一步在我的日常開發(fā)中用處非常大尤其是在聯(lián)調(diào)階段前端只需要看自己負(fù)責(zé)的那部分接口。新建一個配置類Configuration public class OpenApiConfig { Bean public GroupedOpenApi userApi() { return GroupedOpenApi.builder() .group(用戶模塊) .packagesToScan(com.example.demo.controller.user) .build(); } Bean public GroupedOpenApi orderApi() { return GroupedOpenApi.builder() .group(訂單模塊) .packagesToScan(com.example.demo.controller.order) .pathsToMatch(/api/order/**) .build(); } }配置完之后Swagger UI頁面的右上角會多一個下拉框可以直接切換分組。這對接口量超過50個的項目來說非常友好不用在長長的列表里翻來翻去找接口。這里有一個我踩過的坑GroupedOpenApi之間如果掃描范圍重疊會出現(xiàn)同一個接口在多個分組里重復(fù)展示的情況。所以分組的掃描范圍最好做到互斥要么靠不同包名要么靠不同路徑前綴。實在沒辦法重疊的用pathsToMatch把邊界劃清楚比在代碼里寫exclude要省事得多。4. 鑒權(quán)集成與生產(chǎn)級配置4.1 給Swagger UI配置JWT全局鑒權(quán)實際開發(fā)中很多后端接口都要帶Authorization頭才能調(diào)用。如果接口文檔里不帶這個鑒權(quán)能力前端拿去調(diào)試時每個請求都要手動拼Token很煩人??梢栽谂渲妙惱锫暶饕粋€全局的安全方案Bean public OpenAPI customOpenAPI() { return new OpenAPI() .components(new Components().addSecuritySchemes(bearerAuth, new SecurityScheme() .type(SecurityScheme.Type.HTTP) .scheme(bearer) .bearerFormat(JWT))) .addSecurityItem(new SecurityRequirement().addList(bearerAuth)); }加上這段配置后Swagger UI上會多出一個“Authorize”按鈕點擊后輸入Bearer token之后調(diào)接口都會自動帶上這個頭不用再手動填。有個細(xì)節(jié)必須提醒addSecurityItem是全局生效的意味著所有接口都會帶上鑒權(quán)標(biāo)記。如果項目中有一部分接口是公開的比如登錄接口、注冊接口你需要在那些Controller方法上加SecurityRequirements注解來取消這個全局鑒權(quán)否則前端看到登錄接口旁邊有個鎖的圖標(biāo)容易產(chǎn)生疑惑。4.2 Spring Security集成下的過濾器放行大多數(shù)SpringBoot項目都會配上Spring Security而一旦開啟權(quán)限控制Swagger頁面默認(rèn)會被攔截掉顯示403或者跳轉(zhuǎn)到登錄頁。這個問題的本質(zhì)是Swagger的路徑?jīng)]被Spring Security標(biāo)記為放行。我的處理方式是在Security配置里把相關(guān)路徑加進(jìn)白名單Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.authorizeHttpRequests(auth - auth .requestMatchers( /swagger-ui/**, /v3/api-docs/**, /swagger-resources/** ).permitAll() .anyRequest().authenticated()) .csrf(csrf - csrf.ignoringRequestMatchers(/swagger-ui/**, /v3/api-docs/**)); return http.build(); }這個寫法有兩個關(guān)鍵點一是所有Swagger相關(guān)路徑必須加permitAll()二是csrf也要對Swagger路徑放行否則即使權(quán)限放行了Swagger UI頁面可以打開但從頁面發(fā)起API調(diào)用時會被CSRF校驗攔下來前端看到的就是“請求失敗”的紅色提示。如果你用了自定義的登錄頁還需要額外留意Swagger UI里接口的調(diào)用流程。因為Swagger頁面是在前端直接發(fā)Ajax請求的不走原先的表單登錄流程所以你不要指望通過配置一個formLogin就讓Swagger自動拿到登錄態(tài)。要么用上面的Bearer Token方案要么在測試環(huán)境臨時放開鑒權(quán)我一般推薦純Token方案對開發(fā)聯(lián)調(diào)的影響面最小。4.3 OAuth2.0配置的完整示例部分項目走的是OAuth2.0認(rèn)證體系springdoc也支持對接。整體思路和JWT類似只是SecurityScheme的類型換成OAUTH2并額外聲明授權(quán)流程和端點。一個相對完整的寫法new SecurityScheme() .type(SecurityScheme.Type.OAUTH2) .flows(new OAuthFlows() .authorizationCode(new OAuthFlow() .authorizationUrl(http://localhost:8080/oauth/authorize) .tokenUrl(http://localhost:8080/oauth/token) .scopes(new Scopes() .addString(read, 讀取權(quán)限) .addString(write, 寫入權(quán)限))));這個方案寫起來不難但我要潑一盆冷水OAuth2.0授權(quán)碼模式在Swagger UI里的體驗其實不算好。如果你的網(wǎng)關(guān)或者認(rèn)證服務(wù)器做了跨域限制、Cookie策略調(diào)整前端彈出的授權(quán)窗口經(jīng)常會莫名驗證失敗。我實際項目里遇到的情況是開發(fā)環(huán)境勉強能用一上測試環(huán)境就各種跳轉(zhuǎn)問題。后來大家還是改用JWT方案出問題也更好定位。所以除非你們公司規(guī)定必須展示OAuth2授權(quán)流程否則我還是建議優(yōu)先用全局bearerAuth省心太多。4.4 生產(chǎn)環(huán)境的文檔安全策略最后一個部分聊一下發(fā)布問題。很多人習(xí)慣把Swagger一直開著無論什么環(huán)境都暴露對外這在安全層面是個隱患。接口文檔會暴露項目結(jié)構(gòu)、字段定義、接口調(diào)用方式一旦被有心人掃到等于把系統(tǒng)的攻擊面直接畫出來了。我自己的習(xí)慣是開發(fā)環(huán)境打開、測試環(huán)境打開、生產(chǎn)環(huán)境關(guān)閉并且把開關(guān)做成配置項springdoc: api-docs: enabled: ${SWAGGER_ENABLED:false} swagger-ui: enabled: ${SWAGGER_ENABLED:false}部署的時候通過環(huán)境變量SWAGGER_ENABLED來控制。生產(chǎn)環(huán)境設(shè)成false后/v3/api-docs和/swagger-ui.html都會直接返回404不占用額外的接口暴露面。如果你們生產(chǎn)環(huán)境確實需要在線文檔給運維或者客服看建議把Swagger放在內(nèi)網(wǎng)環(huán)境或者通過網(wǎng)關(guān)做權(quán)限控制的路徑上不要直接暴露到公網(wǎng)。這是一個我從上線事故里學(xué)到的教訓(xùn)原因不方便展開只能說“接口文檔裸奔”的下場很不好。5. 常見問題與排查技巧實錄5.1 典型報錯與解決方案我把在SpringBoot 4.0.3下配置Swagger期間遇到的高頻問題整理成了速查表每一條都是親手解決過的現(xiàn)象根因解決方案啟動報錯NoClassDefFoundError引入舊版springdoc或springfox統(tǒng)一用springdoc 3.0.0及配套starter訪問/v3/api-docs返回404路徑掃描配置錯誤或依賴沖突檢查是否有多套Swagger依賴清理干凈頁面打開但接口列表為空packages-to-scan沒寫對確認(rèn)Controller包路徑以及路徑前綴是否匹配接口返回字段全是空值統(tǒng)一返回類型與泛型不兼容用Schema標(biāo)注字段描述避免復(fù)雜泛型UI頁面顯示403Spring Security攔截放行/swagger-ui/和/v3/api-docs/頁面能看但調(diào)用接口報405PathPattern匹配策略變化檢查Requestmapping路徑和Swagger路徑是否沖突啟動后內(nèi)存明顯升高開啟actuator且被掃描設(shè)置show-actuator: false或paths-to-match只選業(yè)務(wù)路徑這里特別點名一下第一條“NoClassDefFoundError”。你如果看了堆棧信息發(fā)現(xiàn)報錯指向io.swagger.v3.oas.models.OpenAPI那基本就是依賴版本沖突。我用mvn dependency:tree看到過項目里同時存在了3.0.0和2.2.0兩個版本的swagger-models原因是某個內(nèi)部SDK傳遞依賴了一個老版本。解決辦法是在pom.xml中用dependencyManagement鎖定版本或者排除內(nèi)部SDK里不需要的Swagger依賴。5.2 請求路徑在Swagger中重復(fù)的坑SpringBoot 4.0.3中如果Controller類上寫了類級別的RequestMapping(/api/user)方法上又寫了GetMapping(/{id})按常理來說Swagger展示的路徑應(yīng)該是/api/user/{id}。但如果你在application.yml里又額外配置了server.servlet.context-path: /demo文檔里會出現(xiàn)兩種情況一種情況是Swagger UI展示的路徑叫作/demo/api/user/{id}前端拿來直接調(diào)本地服務(wù)沒問題另一種情況是Swagger只展示應(yīng)用的內(nèi)部路徑/api/user/{id}而前端實際跑的時候發(fā)現(xiàn)少了前綴怎么調(diào)都是404。這個問題的根源在于context-path是容器層面的路徑Swagger UI無法自動感知它的存在。我的建議是不要在本地開發(fā)環(huán)境配置context-path統(tǒng)一由網(wǎng)關(guān)層做路由前綴這樣才能保證Swagger里看到的路徑和真實接口路徑一致。如果項目已經(jīng)用上了context-path沒法去掉那還有一個臨時方案在OpenAPI配置類里手動拼接全局前綴讓文檔路徑和實際路徑對齊。但這樣做會引入雙層路徑的風(fēng)險一旦網(wǎng)關(guān)也做了前綴會出現(xiàn)兩個前綴疊寫的情況維護(hù)成本很高。5.3 測試環(huán)境接口超時的排查一個很常見但不太容易定位的問題是Swagger UI頁面打開正常接口也能跑但單個接口執(zhí)行特別慢動不動就超時。很多人以為是Swagger影響性能其實多數(shù)情況下和Swagger本身沒有關(guān)系。我有一次排查到深夜才發(fā)現(xiàn)是某個接口的執(zhí)行鏈路里調(diào)用了對外部系統(tǒng)的同步HTTP調(diào)用而那個外部系統(tǒng)在測試環(huán)境不穩(wěn)定每次調(diào)用要卡上好幾秒。和Swagger完全無關(guān)只是以前沒有用Swagger直接調(diào)接口所以沒發(fā)現(xiàn)這個性能瓶頸。這個案例倒提醒了我一件事Swagger不只是文檔工具它還是聯(lián)調(diào)階段最輕量的接口調(diào)試工具。你完全可以在UI上觀察請求耗時快速定位哪些接口存在性能隱患。配合上面提到的display-request-duration: true這個效果會特別明顯能幫助你用很小的成本拿到接口級別的響應(yīng)時間對日常自測來說非常實用。5.4 依賴沖突的終極排查思路前面多次提到依賴沖突這里給出一個標(biāo)準(zhǔn)的排查流程希望幫你少走彎路用mvn dependency:tree查看依賴樹重點搜索包含swagger、springfox、openapi關(guān)鍵字的依賴在pom.xml中對沖突依賴使用exclusion排除多余傳遞依賴全局鎖定Springdoc及Swagger相關(guān)組件版本避免不同模塊各自定義版本清空本地Maven倉庫中殘留的舊包重新編譯啟動時觀察日志中是否有“Swagger”相關(guān)的WARN或ERROR輸出這個步驟其實適用于所有涉及Swagger的SpringBoot版本不只是4.0.3。我在升級大版本時一定會做一輪這樣的清理因為歷史遺留依賴是最容易埋雷的地方。6. 最后再分享兩個細(xì)節(jié)第一件事關(guān)于knife4j。國內(nèi)很多開發(fā)喜歡用這個增強版的Swagger UI界面更美觀也增加了離線文檔導(dǎo)出等功能。但在SpringBoot 4.0.3下它的適配版本和springdoc一樣需要選擇新的就目前實際測試來看兼容情況不如官方Swagger UI穩(wěn)定。如果你不是特別依賴它增強的那些功能我的建議是直接使用官方UI減少一層適配風(fēng)險。第二件事關(guān)于接口文檔的團(tuán)隊協(xié)作。Swagger做到“能看、能調(diào)、能分組”只是基礎(chǔ)更值得投入的是讓它作為團(tuán)隊內(nèi)部接口契約的唯一事實源。我們在實際操作中會把Swagger頁面的URL固化在團(tuán)隊文檔里所有新接手的開發(fā)直接打開頁面查找接口定義不再需要翻閱多人維護(hù)的Word文檔。這樣既避免了文檔內(nèi)容過期也大幅降低了新人的上手成本。回到開頭那位朋友的問題。他后來按我給的版本組合替換了依賴刪掉了多余的舊包并且把Spring Security的放行配置補上前后不到半小時就把Swagger恢復(fù)了。他的原話是“早知道版本對應(yīng)關(guān)系這么重要就不去瞎試了?!边@大概就是我們今天聊這一大圈的核心意義在技術(shù)選型和版本升級時多花點時間確認(rèn)生態(tài)兼容性遠(yuǎn)比試錯來得高效。