解决springboot2.6版本以上使用默认的path_pattern路径匹配模式不兼容swagger问题

问题描述

如果在springboot2.6+中使用springfox swagger即便是最新的3.0版本也会出现NPE(空指针异常) 导致项目启动失败问题。研究其源码发现通过使用自定义一个WebMvcRequestHandlerProvider然后在BeanDefinitionRegistryPostProcessor接口中将已定义的WebMvcRequestHandlerProvider Bean定义替换成步骤三中的即可。只要将步骤一到四中的java文件放到自己项目里就可以在springboot2.6+使用新的path_pattern_parser路径匹配模式而不需要通过兼容配置项(spring.mvc.pathmatch.matching-strategy=ant_path_matcher)指定ant路径匹配模式。

修改WebMvcPatternsRequestConditionWrapper

  1. 注意这里将原泛型参数PatternsRequestCondition换成了PathPatternsRequestCondition
public class WebMvcPatternsRequestConditionWrapper implements springfox.documentation.spring.wrapper.PatternsRequestCondition<PathPatternsRequestCondition> {

    private final String contextPath;
    private final PathPatternsRequestCondition condition;

    public WebMvcPatternsRequestConditionWrapper(String contextPath, PathPatternsRequestCondition condition) {
        this.contextPath = contextPath;
        this.condition = condition;
    }

    @Override
    public springfox.documentation.spring.wrapper.PatternsRequestCondition combine(
            springfox.documentation.spring.wrapper.PatternsRequestCondition<PathPatternsRequestCondition> other) {
        if (other instanceof WebMvcPatternsRequestConditionWrapper && !this.equals(other)) {
            return new WebMvcPatternsRequestConditionWrapper(
                    contextPath,
                    condition.combine(((WebMvcPatternsRequestConditionWrapper) other).condition));
        }
        return this;
    }

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


    @Override
    public boolean equals(Object o) {
        if (o instanceof WebMvcPatternsRequestConditionWrapper) {
            return this.condition.equals(((WebMvcPatternsRequestConditionWrapper) o).condition);
        }
        return false;
    }

    @Override
    public int hashCode() {
        return this.condition.hashCode();
    }


    @Override
    public String toString() {
        return this.condition.toString();
    }
}

修改WebMvcRequestHandler

  1. 注意这里修改项使将getPatternsCondition()方法中向构建的WebMvcPatternsRequestConditionWrapper对象的构造器传入的也是PathPatternsRequestCondition
public class WebMvcRequestHandler implements RequestHandler {
    private final String contextPath;
    private final HandlerMethodResolver methodResolver;
    private final RequestMappingInfo requestMapping;
    private final HandlerMethod handlerMethod;

    public WebMvcRequestHandler(
            String contextPath,
            HandlerMethodResolver methodResolver,
            RequestMappingInfo requestMapping,
            HandlerMethod handlerMethod) {
        this.contextPath = contextPath;
        this.methodResolver = methodResolver;
        this.requestMapping = requestMapping;
        this.handlerMethod = handlerMethod;
    }

    @Override
    public HandlerMethod getHandlerMethod() {
        return handlerMethod;
    }

    @Override
    public RequestHandler combine(RequestHandler other) {
        return this;
    }

    @Override
    public Class<?> declaringClass() {
        return handlerMethod.getBeanType();
    }

    @Override
    public boolean isAnnotatedWith(Class<? extends Annotation> annotation) {
        return null != AnnotationUtils.findAnnotation(handlerMethod.getMethod(), annotation);
    }

    @Override
    public PatternsRequestCondition getPatternsCondition() {
        return new WebMvcPatternsRequestConditionWrapper(contextPath,requestMapping.getPathPatternsCondition());
    }

    @Override
    public String groupName() {
        return ControllerNamingUtils.controllerNameAsGroup(handlerMethod);
    }

    @Override
    public String getName() {
        return handlerMethod.getMethod().getName();
    }

    @Override
    public Set<RequestMethod> supportedMethods() {
        return requestMapping.getMethodsCondition().getMethods();
    }

    @Override
    public Set<MediaType> produces() {
        return requestMapping.getProducesCondition().getProducibleMediaTypes();
    }

    @Override
    public Set<MediaType> consumes() {
        return requestMapping.getConsumesCondition().getConsumableMediaTypes();
    }

    @Override
    public Set<NameValueExpression<String>> headers() {
        return WebMvcNameValueExpressionWrapper.from(requestMapping.getHeadersCondition().getExpressions());
    }

    @Override
    public Set<NameValueExpression<String>> params() {
        return WebMvcNameValueExpressionWrapper.from(requestMapping.getParamsCondition().getExpressions());
    }

    @Override
    public <T extends Annotation> Optional<T> findAnnotation(Class<T> annotation) {
        return ofNullable(AnnotationUtils.findAnnotation(handlerMethod.getMethod(), annotation));
    }

    @Override
    public RequestHandlerKey key() {
        return new RequestHandlerKey(
                requestMapping.getPathPatternsCondition().getDirectPaths(),
                requestMapping.getMethodsCondition().getMethods(),
                requestMapping.getConsumesCondition().getConsumableMediaTypes(),
                requestMapping.getProducesCondition().getProducibleMediaTypes());
    }

    @Override
    public springfox.documentation.spring.wrapper.RequestMappingInfo<?> getRequestMapping() {
        return new WebMvcRequestMappingInfoWrapper(requestMapping);
    }

    @Override
    public List<ResolvedMethodParameter> getParameters() {
        return methodResolver.methodParameters(handlerMethod);
    }

    @Override
    public ResolvedType getReturnType() {
        return methodResolver.methodReturnType(handlerMethod);
    }

    @Override
    public <T extends Annotation> Optional<T> findControllerAnnotation(Class<T> annotation) {
        return ofNullable(AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), annotation));
    }

    @Override
    public String toString() {
        return new StringJoiner(", ", WebMvcRequestHandler.class.getSimpleName() + "{", "}")
                .add("requestMapping=" + requestMapping)
                .add("handlerMethod=" + handlerMethod)
                .add("key=" + key())
                .toString();
    }
}

修改WebMvcRequestHandlerProvider

  1. 注意方法toRequestHandler()中使用WebMvcRequestHandler就是前面提到的
public class WebMvcRequestHandlerProvider implements RequestHandlerProvider {
    private final List<RequestMappingInfoHandlerMapping> handlerMappings;
    private final HandlerMethodResolver methodResolver;
    private final String contextPath;

    public WebMvcRequestHandlerProvider(Optional<ServletContext> servletContext,HandlerMethodResolver methodResolver,List<RequestMappingInfoHandlerMapping> handlerMappings) {
        this.handlerMappings = handlerMappings;
        this.methodResolver = methodResolver;
        this.contextPath = servletContext.map(ServletContext::getContextPath).orElse(ROOT);
    }

    @Override
    public List<RequestHandler> requestHandlers() {
        return nullToEmptyList(handlerMappings).stream()
                .filter(requestMappingInfoHandlerMapping ->
                        !("org.springframework.integration.http.inbound.IntegrationRequestMappingHandlerMapping"
                                .equals(requestMappingInfoHandlerMapping.getClass()
                                        .getName())))
                .map(toMappingEntries())
                .flatMap((entries -> StreamSupport.stream(entries.spliterator(), false)))
                .map(toRequestHandler())
                .sorted(byPatternsCondition())
                .collect(toList());
    }

    private Function<RequestMappingInfoHandlerMapping,
            Iterable<Map.Entry<RequestMappingInfo, HandlerMethod>>> toMappingEntries() {
        return input -> input.getHandlerMethods().entrySet();
    }

    private Function<Map.Entry<RequestMappingInfo, HandlerMethod>, RequestHandler> toRequestHandler() {
        return input -> new WebMvcRequestHandler(
                contextPath,
                methodResolver,
                input.getKey(),
                input.getValue());
    }
}

替换WebMvcRequestHandlerProvider的Bean定义

  1. 使用BeanDefinitionRegistryPostProcessor接口替换已存在的WebMvcRequestHandlerProvider的Bean定义
    public class CompatiblePathPatternParser implements BeanDefinitionRegistryPostProcessor, EnvironmentAware {
        static final String BEAN_NAME = "webMvcRequestHandlerProvider";

        private Environment environment;

        @Override
        public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
            WebMvcProperties.MatchingStrategy strategy = environment.getProperty("spring.mvc.pathmatch.matching-strategy", WebMvcProperties.MatchingStrategy.class);
            if(registry.containsBeanDefinition(BEAN_NAME) && strategy != WebMvcProperties.MatchingStrategy.ANT_PATH_MATCHER){
                registry.removeBeanDefinition(BEAN_NAME);
                registry.registerBeanDefinition(BEAN_NAME,new RootBeanDefinition(WebMvcRequestHandlerProvider.class));
            }
        }

        @Override
        public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

        }

        @Override
        public void setEnvironment(Environment environment) {
            this.environment = environment;
        }

    }

你可能感兴趣的:(技能,#,Spring,java,spring,swagger,spring,boot,spring,cloud)