由于Swagger
停更,导致其默认配置下对于高版本的Spring Boot
的不兼容
原因是Spring MVC
将默认的路径匹配策略由MatchingStrategy.ANT_PATH_MATCHER替换为MatchingStrategy.PATH_PATTERN_PARSER
因此需要修改匹配策略
spring:
mvc:
path-match:
matching-strategy: ant_path_matcher
但对于更高的版本,依然存在问题
DocumentationPluginsBootstrapper报错,出现了NullPointerException
根据日志中的空指针信息,定位到springfox.documentation.spring.web.WebMvcPatternsRequestConditionWrapper的getPatterns
方法
@Override
public Set<String> getPatterns() {
return this.condition.getPatterns().stream()
.map(p -> String.format("%s/%s", maybeChompTrailingSlash(contextPath), maybeChompLeadingSlash(p)))
.collect(Collectors.toSet());
}
显然是由于condition
为null导致的,追查condition
的相关使用
重新启动
定位到springfox.documentation.spring.web.WebMvcRequestHandler中的requestMapping
,其类为org.springframework.web.servlet.mvc.method.RequestMappingInfo
继续查看getPatternsCondition
方法
@Nullable
public PatternsRequestCondition getPatternsCondition() {
return this.patternsCondition;
}
重新启动,定位到调用者为org.springframework.web.servlet.mvc.method.RequestMappingInfo$DefaultBuilder中的build
方法
@Override
@SuppressWarnings("deprecation")
public RequestMappingInfo build() {
PathPatternsRequestCondition pathPatterns = null;
PatternsRequestCondition patterns = null;
if (this.options.patternParser != null) {
pathPatterns = (ObjectUtils.isEmpty(this.paths) ?
EMPTY_PATH_PATTERNS :
new PathPatternsRequestCondition(this.options.patternParser, this.paths));
}
else {
patterns = (ObjectUtils.isEmpty(this.paths) ?
EMPTY_PATTERNS :
new PatternsRequestCondition(
this.paths, null, this.options.getPathMatcher(),
this.options.useSuffixPatternMatch(), this.options.useTrailingSlashMatch(),
this.options.getFileExtensions()));
}
ContentNegotiationManager manager = this.options.getContentNegotiationManager();
return new RequestMappingInfo(
this.mappingName, pathPatterns, patterns,
ObjectUtils.isEmpty(this.methods) ?
EMPTY_REQUEST_METHODS : new RequestMethodsRequestCondition(this.methods),
ObjectUtils.isEmpty(this.params) ?
EMPTY_PARAMS : new ParamsRequestCondition(this.params),
ObjectUtils.isEmpty(this.headers) ?
EMPTY_HEADERS : new HeadersRequestCondition(this.headers),
ObjectUtils.isEmpty(this.consumes) && !this.hasContentType ?
EMPTY_CONSUMES : new ConsumesRequestCondition(this.consumes, this.headers),
ObjectUtils.isEmpty(this.produces) && !this.hasAccept ?
EMPTY_PRODUCES : new ProducesRequestCondition(this.produces, this.headers, manager),
this.customCondition != null ?
new RequestConditionHolder(this.customCondition) : EMPTY_CUSTOM,
this.options);
}
显然patterns
是否为null是由options
(org.springframework.web.servlet.mvc.method.RequestMappingInfo$BuilderConfiguration)的patternParser
是否为null决定的
查看patternsCondition
使用情况
重新启动,调用者为org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping的afterPropertiesSet
方法
@Override
@SuppressWarnings("deprecation")
public void afterPropertiesSet() {
this.builderConfig = new RequestMappingInfo.BuilderConfiguration();
if (getPatternParser() != null) {
this.builderConfig.setPatternParser(getPatternParser());
}
else {
this.builderConfig.setPathMatcher(null);
this.builderConfig.setTrailingSlashMatch(true);
this.builderConfig.setSuffixPatternMatch(false);
}
super.afterPropertiesSet();
}
afterPropertiesSet
方法正是Spring
中Bean
的初始化方法
查看getPatternParser
方法,其属于父类org.springframework.web.servlet.handler.AbstractHandlerMapping
@Nullable
public PathPatternParser getPatternParser() {
return this.patternParser;
}
根据类org.springframework.boot.actuate.endpoint.web.servlet.WebMvcEndpointHandlerMapping,可搜索到相关Bean
配置类为org.springframework.boot.actuate.autoconfigure.endpoint.web.servlet.WebMvcEndpointManagementContextConfiguration
@Bean
@ConditionalOnMissingBean
public WebMvcEndpointHandlerMapping webEndpointServletHandlerMapping(WebEndpointsSupplier webEndpointsSupplier,
ServletEndpointsSupplier servletEndpointsSupplier, ControllerEndpointsSupplier controllerEndpointsSupplier,
EndpointMediaTypes endpointMediaTypes, CorsEndpointProperties corsProperties,
WebEndpointProperties webEndpointProperties, Environment environment) {
List<ExposableEndpoint<?>> allEndpoints = new ArrayList<>();
Collection<ExposableWebEndpoint> webEndpoints = webEndpointsSupplier.getEndpoints();
allEndpoints.addAll(webEndpoints);
allEndpoints.addAll(servletEndpointsSupplier.getEndpoints());
allEndpoints.addAll(controllerEndpointsSupplier.getEndpoints());
String basePath = webEndpointProperties.getBasePath();
EndpointMapping endpointMapping = new EndpointMapping(basePath);
boolean shouldRegisterLinksMapping = shouldRegisterLinksMapping(webEndpointProperties, environment, basePath);
return new WebMvcEndpointHandlerMapping(endpointMapping, webEndpoints, endpointMediaTypes,
corsProperties.toCorsConfiguration(), new EndpointLinksResolver(allEndpoints, basePath),
shouldRegisterLinksMapping, WebMvcAutoConfiguration.pathPatternParser);
}
构造方法的最后一个参数为WebMvcAutoConfiguration.pathPatternParser,基本可以确定是它了
查看构造方法
public WebMvcEndpointHandlerMapping(EndpointMapping endpointMapping, Collection<ExposableWebEndpoint> endpoints,
EndpointMediaTypes endpointMediaTypes, CorsConfiguration corsConfiguration,
EndpointLinksResolver linksResolver, boolean shouldRegisterLinksMapping,
PathPatternParser pathPatternParser) {
super(endpointMapping, endpoints, endpointMediaTypes, corsConfiguration, shouldRegisterLinksMapping,
pathPatternParser);
this.linksResolver = linksResolver;
setOrder(-100);
}
继续查看父类org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMapping的构造方法
public AbstractWebMvcEndpointHandlerMapping(EndpointMapping endpointMapping,
Collection<ExposableWebEndpoint> endpoints, EndpointMediaTypes endpointMediaTypes,
CorsConfiguration corsConfiguration, boolean shouldRegisterLinksMapping,
PathPatternParser pathPatternParser) {
this.endpointMapping = endpointMapping;
this.endpoints = endpoints;
this.endpointMediaTypes = endpointMediaTypes;
this.corsConfiguration = corsConfiguration;
this.shouldRegisterLinksMapping = shouldRegisterLinksMapping;
setPatternParser(pathPatternParser);
setOrder(-100);
}
继续查看setPatternParser
方法,其属于父类org.springframework.web.servlet.handler.AbstractHandlerMethodMapping
@Override
public void setPatternParser(PathPatternParser patternParser) {
Assert.state(this.mappingRegistry.getRegistrations().isEmpty(),
"PathPatternParser must be set before the initialization of " +
"request mappings through InitializingBean#afterPropertiesSet.");
super.setPatternParser(patternParser);
}
最后,调用了父类org.springframework.web.servlet.handler.AbstractHandlerMapping的setPatternParser
方法
public void setPatternParser(PathPatternParser patternParser) {
this.patternParser = patternParser;
}
问题的源头为WebMvcEndpointHandlerMapping中的patternParser
不为null,只需将其设置为null即可解决问题,方法如下:
Bean
被 @ConditionalOnMissingBean 注解,重新定义一个WebMvcEndpointHandlerMapping的Bean
覆盖即可setPatternParser
方法是public
的,且使用方法为afterPropertiesSet
,可实现org.springframework.beans.factory.config.BeanPostProcessor接口的postProcessBeforeInitialization
方法,将patternParser
设置为null@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean.getClass().equals(WebMvcEndpointHandlerMapping.class)) {
((WebMvcEndpointHandlerMapping) bean).setPatternParser(null);
}
return bean;
}