Swagger与高版本Spring Boot不兼容问题

前言

由于Swagger停更,导致其默认配置下对于高版本的Spring Boot的不兼容
原因是Spring MVC将默认的路径匹配策略由MatchingStrategy.ANT_PATH_MATCHER替换为MatchingStrategy.PATH_PATTERN_PARSER
因此需要修改匹配策略

spring:
  mvc:
    path-match:
      matching-strategy: ant_path_matcher

但对于更高的版本,依然存在问题

问题

DocumentationPluginsBootstrapper报错,出现了NullPointerException
Swagger与高版本Spring Boot不兼容问题_第1张图片

追查

根据日志中的空指针信息,定位到springfox.documentation.spring.web.WebMvcPatternsRequestConditionWrappergetPatterns方法

@Override
public Set<String> getPatterns() {
	return this.condition.getPatterns().stream()
		.map(p -> String.format("%s/%s", maybeChompTrailingSlash(contextPath),  maybeChompLeadingSlash(p)))
		.collect(Collectors.toSet());
}

显然是由于conditionnull导致的,追查condition的相关使用
Swagger与高版本Spring Boot不兼容问题_第2张图片

唯一的赋值方式在构造方法中,打上断点
Swagger与高版本Spring Boot不兼容问题_第3张图片

重新启动
Swagger与高版本Spring Boot不兼容问题_第4张图片
定位到springfox.documentation.spring.web.WebMvcRequestHandler中的requestMapping,其类为org.springframework.web.servlet.mvc.method.RequestMappingInfo
继续查看getPatternsCondition方法

@Nullable
public PatternsRequestCondition getPatternsCondition() {
	return this.patternsCondition;
}

查看patternsCondition使用情况
Swagger与高版本Spring Boot不兼容问题_第5张图片

同样是在构造方法中赋值,打上断点
Swagger与高版本Spring Boot不兼容问题_第6张图片

重新启动,定位到调用者为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是由optionsorg.springframework.web.servlet.mvc.method.RequestMappingInfo$BuilderConfiguration)的patternParser是否为null决定的
查看patternsCondition使用情况
Swagger与高版本Spring Boot不兼容问题_第7张图片

找到赋值方法并打上断点
Swagger与高版本Spring Boot不兼容问题_第8张图片

重新启动,调用者为org.springframework.boot.actuate.endpoint.web.servlet.AbstractWebMvcEndpointHandlerMappingafterPropertiesSet方法

@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方法正是SpringBean的初始化方法
查看getPatternParser方法,其属于父类org.springframework.web.servlet.handler.AbstractHandlerMapping

@Nullable
public PathPatternParser getPatternParser() {
	return this.patternParser;
}

查看当前类
Swagger与高版本Spring Boot不兼容问题_第9张图片

根据类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.AbstractHandlerMappingsetPatternParser方法

public void setPatternParser(PathPatternParser patternParser) {
	this.patternParser = patternParser;
}

解决

问题的源头为WebMvcEndpointHandlerMapping中的patternParser不为null,只需将其设置为null即可解决问题,方法如下:

  • 由于WebMvcEndpointHandlerMappingBean@ConditionalOnMissingBean 注解,重新定义一个WebMvcEndpointHandlerMappingBean覆盖即可
  • 由于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;
    }
    

你可能感兴趣的:(spring,boot)