Spring Boot2.6之后,swagger的包,WebMvcPatternsRequestConditionWrapper.getPatterns NPE 异常
文章目录
报错现象
堆栈
2022-03-13 02:40:28.000 ERROR [16312] --- [ main] o.s.boot.SpringApplication : Application run failed
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.access$200(DefaultLifecycleProcessor.java:54)
at org.springframework.context.support.DefaultLifecycleProcessor$LifecycleGroup.start(DefaultLifecycleProcessor.java:356)
at java.base/java.lang.Iterable.forEach(Iterable.java:75)
at org.springframework.context.support.DefaultLifecycleProcessor.startBeans(DefaultLifecycleProcessor.java:155)
at org.springframework.context.support.DefaultLifecycleProcessor.onRefresh(DefaultLifecycleProcessor.java:123)
at org.springframework.context.support.AbstractApplicationContext.finishRefresh(AbstractApplicationContext.java:935)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:586)
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:145)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:740)
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:415)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:303)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1312)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1301)
at com.example.demo1.Demo1Application.main(Demo1Application.java:22)
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.base/java.util.Comparator.lambda$comparing$77a9974f$1(Comparator.java:469)
at java.base/java.util.TimSort.countRunAndMakeAscending(TimSort.java:355)
at java.base/java.util.TimSort.sort(TimSort.java:234)
at java.base/java.util.Arrays.sort(Arrays.java:1515)
at java.base/java.util.ArrayList.sort(ArrayList.java:1750)
at java.base/java.util.stream.SortedOps$RefSortingSink.end(SortedOps.java:392)
at java.base/java.util.stream.Sink$ChainedReference.end(Sink.java:258)
at java.base/java.util.stream.Sink$ChainedReference.end(Sink.java:258)
at java.base/java.util.stream.Sink$ChainedReference.end(Sink.java:258)
at java.base/java.util.stream.Sink$ChainedReference.end(Sink.java:258)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:485)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:913)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:578)
at springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider.requestHandlers(WebMvcRequestHandlerProvider.java:81)
at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:195)
at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1655)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474)
at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:913)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:578)
at springfox.documentation.spring.web.plugins.AbstractDocumentationPluginsBootstrapper.withDefaults(AbstractDocumentationPluginsBootstrapper.java:107)
at springfox.documentation.spring.web.plugins.AbstractDocumentationPluginsBootstrapper.buildContext(AbstractDocumentationPluginsBootstrapper.java:91)
at springfox.documentation.spring.web.plugins.AbstractDocumentationPluginsBootstrapper.bootstrapDocumentationPlugins(AbstractDocumentationPluginsBootstrapper.java:82)
at springfox.documentation.spring.web.plugins.DocumentationPluginsBootstrapper.start(DocumentationPluginsBootstrapper.java:100)
at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:178)
... 14 common frames omitted
截图
排查过程
首先在NPE地方打断点,发现是 this.condition 为 null 导致的,那就看它哪里set了
Ctrl + F 搜一下发现只有构造方法有赋值语句,那就在这里打断点吧,然后重启debug,再次来到这里
- 顺着堆栈往上找,发现是
springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider
在返回 requestHandlers 时赋值的。 - 我们来看这个值来源是什么,顺着stream的代码,找到了原来时取handlerMappings中的字段,发现是
org.springframework.web.servlet.mvc.method.RequestMappingInfo#pathPatternsCondition
字段
- 这个类是Spring的代码,我们直接看定义处,发现本身就可能为空,说明这是SpringFox代码的一个bug
- 使用这么广的开源软件居然有bug,怀疑是没及时更新导致,看了一下最新的3.0.0其实是2020.7发布的,怪不得和spring boot最新版不兼容
- 再来看为啥2.6之前就没事呢?去 spring 官网看看2.6的更新,其中有一条
请求路径与 Spring MVC 处理映射匹配的默认策略已从AntPathMatcher更改为PathPatternParser。
你可以设置spring.mvc.pathmatch.matching-strategy为ant-path-matcher来改变它。
原来是默认的 path 解析策略调整了,所以我列了以下几个解决方案
解决方案
方案 | 成本 | 对比 |
---|---|---|
设置 application.properties 属性还原成 antMacherspring.mvc.pathmatch.matching-strategy为ant-path-matcher | 最低 | 属于临时快速兼容方案,AntPathMatcher性能不如PathPatternParser,功能强大一点,且springfox很久不发新版本,不建议继续使用 |
将 SpringFox 换成 SpringDoc | 中 | 代码注解将由Swagger配置改为标准的OpenAPI3配置,见另一篇文章 |
克隆springfox代码,自己拉分支修完发版本,程序中再依赖 | 较高 | springfox很久不发新版本,不建议继续使用,否则会有长期自主维护成本 |
通过代理,劫持SpringFox的方法,过滤掉为null的接口 | 低 | 不建议,antMatch匹配的的接口都是空,会导致所有接口都不出现在接口文档上,相当于没有使用 swagge,不如直接把swagger排除掉 |
劫持SpringFox的方法,避免NPE
/**
* Spring Boot 2.6 与 swagger 不兼容解决
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(WebMvcRequestHandlerProvider.class)
public static class FixNpeForSpringfoxHandlerProviderBeanPostProcessorConfiguration {
@Bean
public static BeanPostProcessor springfoxHandlerProviderBeanPostProcessor() {
return new BeanPostProcessor() {
@Override
public Object postProcessAfterInitialization(@Nonnull Object bean, @Nonnull String beanName) throws BeansException {
if (bean instanceof WebMvcRequestHandlerProvider || bean instanceof WebFluxRequestHandlerProvider) {
customizeSpringfoxHandlerMappings(getHandlerMappings(bean));
}
return bean;
}
private <T extends RequestMappingInfoHandlerMapping> void customizeSpringfoxHandlerMappings(List<T> mappings) {
List<T> copy = mappings.stream()
.filter(mapping -> mapping.getPatternParser() == null)
.collect(Collectors.toList());
mappings.clear();
mappings.addAll(copy);
}
@SuppressWarnings("unchecked")
private List<RequestMappingInfoHandlerMapping> getHandlerMappings(Object bean) {
try {
Field field = ReflectionUtils.findField(bean.getClass(), "handlerMappings");
field.setAccessible(true);
return (List<RequestMappingInfoHandlerMapping>) field.get(bean);
} catch (IllegalArgumentException | IllegalAccessException e) {
throw new IllegalStateException(e);
}
}
};
}
}