這個 NPE 是 Spring Boot 2.6 改預設路徑匹配策略引發的。
springfox 3.0.0只認AntPathMatcher,撞上新預設的PathPatternParser就空指針。要麼把匹配策略設回ant_path_matcher,要麼直接遷到還在維護的springdoc-openapi。
Issue & Env(案發現場與環境)
- 業務場景:項目從 Spring Boot 2.5 升到 2.6 及以上,Swagger 文檔接口訪問不了,應用啟動即失敗。
- 環境:Spring Boot 2.6.x(含 2.7.x)+ springfox 3.0.0 + JDK 8/11/17 均可復現。springfox 官方 issue #3909 標註為
wontfix,即框架自身不會修。
StackTrace(堆疊日誌)
啟動階段 documentationPluginsBootstrapper 這個 Bean 起不來,NPE 落在 springfox 讀路徑模式的地方:
org.springframework.context.ApplicationContextException: Failed to start bean 'documentationPluginsBootstrapper'; nested exception is java.lang.NullPointerException
at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:181)
at org.springframework.context.support.DefaultLifecycleProcessor.lambda$startBeans$1(DefaultLifecycleProcessor.java:152)
at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1767)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:750)
Caused by: java.lang.NullPointerException: null
at springfox.documentation.spring.web.WebMvcPatternsRequestConditionWrapper.getPatterns(WebMvcPatternsRequestConditionWrapper.java:56)
at springfox.documentation.RequestHandler.sortedPaths(RequestHandler.java:113)
at springfox.documentation.spi.service.contexts.Orderings.lambda$byPatternsCondition$3(Orderings.java:89)
at java.util.Comparator.lambda$comparing$77a9974f$1(Comparator.java:469)
at java.util.TimSort.countRunAndMakeAscending(TimSort.java:355)
at java.util.TimSort.sort(TimSort.java:220)
at java.util.Arrays.sort(Arrays.java:1512)
at java.util.stream.SortedOps$SizedRefSortingSink.end(SortedOps.java:349)
at java.util.stream.Sink$ChainedReference.end(Sink.java:258)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:485)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:498)
at springfox.documentation.spring.web.plugins.DocumentationPluginsBootstrapper.start(DocumentationPluginsBootstrapper.java:100)
at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:178)
... 5 more
現在預設策略換成了 PathPatternParser,而 WebMvcPatternsRequestConditionWrapper.getPatterns() 裡拿到的 PathPattern 與 springfox 預期的 AntPathMatcher 結構對不上,觸發了空指針。
Reproduce(復現程式碼)
pom 引入 springfox 3.0.0(這是最後發佈的版本,已停更):
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.2</version>
</parent>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
寫一個帶 Swagger 註解的接口:
@RestController
public class DocController {
@GetMapping("/hello")
@ApiOperation("打招呼")
public String hello() {
return "hi";
}
}
啟動即拋上面的 NPE。注意:環境是 Spring Boot 2.6+(含 2.7.x)時才穩定復現,2.5.x 及更早不會。
Root Cause(根因分析)
Spring Boot 2.6.0 把 Spring MVC 的預設路徑匹配策略,從 AntPathMatcher 換成了 PathPatternParser(PathMatchConfigurer 的預設值變化)。PathPatternParser 性能更好,也能顯式擴展,但 API 和 AntPathMatcher 完全不同。
springfox 3.0.0 在掃描請求處理器時,直接假設 MVC 用的是 AntPathMatcher,程式碼路徑走到了 WebMvcPatternsRequestConditionWrapper.getPatterns(),從 PathPatternParser 生成的物件裡取自己預期的東西,結果拿到 null 或對不上,拋出 NullPointerException。
springfox 項目已經長期不活躍,3.0.0 是 2020 年的發佈,此後沒有再跟進 Spring Boot 2.6 的這次變更。所以這是環境升級和依賴過期的「雙向奔赴」,不是應用程式碼寫錯。
The Fix(解決方案)
方案一:把匹配策略設回 ant_path_matcher(臨時繞過)
在 application.yml 裡:
spring:
mvc:
pathmatch:
matching-strategy: ant_path_matcher
application.properties 同理:
spring.mvc.pathmatch.matching-strategy=ant_path_matcher
效果是讓 MVC 回到 springfox 適應的那套匹配策略,NPE 消失。代價是放棄了 PathPatternParser 的性能與擴展能力,而且這是給一個已停止維護的框架續命,屬於過渡手段。有報 ANT_PATH_MATCHER 未生效的情況,多是同時用了 Actuator 的 WebMvcEndpointHandlerMapping,需要額外處理,見 issue #3462 的討論。
方案二:遷移到 springdoc-openapi(推薦)
springfox 已停更,長線上應該換成還在活躍維護的 springdoc-openapi。Starter 名和註解對應關係:
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.6.0</version>
</dependency>
它原生兼容 PathPatternParser,不需要改任何匹配策略。註解從 @Api/@ApiOperation 換成 OpenAPI 3 的 @Operation 等,文檔地址從 /swagger-ui.html 變成 /swagger-ui/index.html。好處是不靠「繞行參數」支撐,能從根上擺脫這次版本兼容問題。
