• 解决swagger和自定义参数解析器的功能冲突问题


    在上一篇文章spring mvc请求体偷梁换柱:HandlerMethodArgumentResolver
    中,讲解了如何使用spring mvc中的参数解析器解密传入的字符串并反序列化的方法,大大提高了代码的可读性和可复用性,但是却遗留了一个问题:springmvc自定义参数解析器的参数上不能再带上@RequestBody注解,否则会被RequestResponseBodyMethodProcessor类提前适配参数类型,自定义的参数解析器就会失效,这带来的后果就是swagger会无法识别正确的参数类型,将请求体识别为Query Params,然后将body展开

    image-20211011154924712

    可以看到,所有参数都被识别为ModelAttribute类型(query标志)。正确的格式应当是如下样子

    2021-10-11_155520

    一、问题产生的原因

    产生这个问题的根本原因就是spring mvc和swagger都对@RequestBody注解进行了单独的判定,功能上都依赖于该注解本身。

    1.springmvc对@RequestBody注解的依赖

    就拿当前自定义的参数解析器来说,如果对请求参数加上了@RequestBody注解,对参数的反序列化会提前被RequestResponseBodyMethodProcessor拦截,自定义的参数解析器会失效。

    具体源代码位置:https://github.com/spring-projects/spring-framework/blob/5.2.x/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestResponseBodyMethodProcessor.java#L111

    image-20211011160848305

    可以看到,该参数解析器对加上@ReuqestBody注解的参数都支持解析,然后做序列化的操作,然而它在参数解析器列表中的优先级比较高,自定义的参数解析器添加到参数解析器列表中之后会排在它的后面,所以如果加上@RequestBody注解,自定义的参数解析器就失效了。

    因此使用自定义参数解析器一定不能使用@RequestBody注解

    下图源代码位置:https://github.com/spring-projects/spring-framework/blob/5.2.x/spring-web/src/main/java/org/springframework/web/method/support/HandlerMethodArgumentResolverComposite.java#L129

    image-20211009151851091

    2.swagger对@Requestbody的依赖

    经过调用栈追踪,最终发现在两个地方的功能会对@RequestBody注解有单独判定

    • 请求类型判定,也就是说POST请求请求类型是哪种类型,这决定了入参是否会作为Request Parameter被展开参数,也就是开篇文章中的第一张图,整个model都被视为ModelAttribute展开了。
    • Definition属性值填充,这确保被@RequestBody注解修饰的入参会被正常显示,如本篇文章第二张图片所示。

    2.1 请求类型判定

    源代码位置:https://github.com/springfox/springfox/blob/2.9.2/springfox-spring-web/src/main/java/springfox/documentation/spring/web/readers/operation/OperationParameterReader.java#L151

    image-20211011163142881

    这里对RequestBody等常用注解进行了单独的判定,确保这些注解修饰的入参不会被作为RequestParam展开。

    2.2 Definition属性值填充

    Definition属性中填充了入参、出参等参数类型,如果没有相应的Model定义,则swagger信息就会是不完整的,在浏览器页面中的显示也会是不全的。填充Definition的逻辑也依赖于@RequestBody注解。

    源代码位置:https://github.com/springfox/springfox/blob/2.9.2/springfox-spring-web/src/main/java/springfox/documentation/spring/web/readers/operation/OperationModelsProvider.java#L80

    image-20211011164020729

    可以看到,只有被RequestBody注解和RequestPart注解修饰的入参才会被接收进入Definition属性。

    综合2.1和2.2的源代码分析,可以看到,swagger功能依赖于@RequestBody注解,入参如果不被该注解修饰,则swagger功能就会不完整,这和在springmvc中使用独立的参数解析器功能不得使用@RequestBody注解矛盾。

    二、解决问题

    从以上分析可以得到结论,这里的根本问题是springmvc中独立的参数解析器功能和swagger功能上的冲突,一个要求不能加上@RequestBody注解,一个要求必须加上@RequestBody注解,所以解决方法上可以使用两种方式

    • 从springmvc入手,想办法提高自定义参数解析器的优先级,只要自定义的参数解析器优先级比RequestResponseBodyMethodProcessor高,则就可以在自定义的参数上加上@RequestBody注解,swagger功能自然而然就能正常了。
    • 从swagger入手,想办法解决掉上面两部分对@RequestBody的单独判定,不修改springmvc相关功能也可以让swagger功能正常。

    考虑到修改springmvc功能可能会对以后的版本升级造成较大影响,这里决定利用切面修改原有的swagger对@RequestBody的两个地方的行为,从而让swagger功能正常。

    1.请求类型判定的逻辑调整

    首先,定义一个注解

    @Documented
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.PARAMETER})
    public @interface NoSwaggerExpand {
    
        /**
         * default swagger expand disable
         * @see OperationParameterReader#shouldExpand(springfox.documentation.service.ResolvedMethodParameter, com.fasterxml.classmate.ResolvedType)
         */
        boolean expand() default false;
    }
    

    将其加到入参上

        @ApiOperation(value = "demo", notes = "demo")
        @PostMapping(value = "/test")
        public Result<boolean> test(@HdxDecrypt @NoSwaggerExpand @ApiParam(required = true) ReqDTO reqDTO) {
            try {
                log.info(ObjectMapperFactory.getObjectMapper().writeValueAsString(reqDTO));
            } catch (JsonProcessingException e) {
                log.error("", e);
            }
            return null;
        }
    

    然后定义切面

    import com.cosmoplat.qdind.swagger.annotation.NoSwaggerExpand;
    import com.fasterxml.classmate.ResolvedType;
    import com.google.common.base.Predicate;
    import com.google.common.collect.FluentIterable;
    import lombok.extern.slf4j.Slf4j;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RequestPart;
    import springfox.documentation.builders.ParameterBuilder;
    import springfox.documentation.service.Parameter;
    import springfox.documentation.service.ResolvedMethodParameter;
    import springfox.documentation.spi.schema.EnumTypeDeterminer;
    import springfox.documentation.spi.service.contexts.OperationContext;
    import springfox.documentation.spi.service.contexts.ParameterContext;
    import springfox.documentation.spring.web.plugins.DocumentationPluginsManager;
    import springfox.documentation.spring.web.readers.parameter.ExpansionContext;
    import springfox.documentation.spring.web.readers.parameter.ModelAttributeParameterExpander;
    
    import java.lang.annotation.Annotation;
    import java.util.List;
    import java.util.Optional;
    import java.util.Set;
    
    import static com.google.common.base.Predicates.not;
    import static com.google.common.collect.Lists.newArrayList;
    import static springfox.documentation.schema.Collections.isContainerType;
    import static springfox.documentation.schema.Maps.isMapType;
    import static springfox.documentation.schema.Types.isBaseType;
    import static springfox.documentation.schema.Types.typeNameFor;
    
    /**
     * @author kdyzm
     * @date 2021/10/11
     */
    @Slf4j
    @Aspect
    @Component
    public class SwaggerExpandAspect {
    
        private final ModelAttributeParameterExpander expander;
        private final EnumTypeDeterminer enumTypeDeterminer;
    
        @Autowired
        private DocumentationPluginsManager pluginsManager;
    
        @Autowired
        public SwaggerExpandAspect(
                ModelAttributeParameterExpander expander,
                EnumTypeDeterminer enumTypeDeterminer) {
            this.expander = expander;
            this.enumTypeDeterminer = enumTypeDeterminer;
        }
    
        @Around("execution(* springfox.documentation.spring.web.readers.operation.OperationParameterReader.apply(..))")
        public Object pointCut(ProceedingJoinPoint point) throws Throwable {
            Object[] args = point.getArgs();
            OperationContext context = (OperationContext) args[0];
            context.operationBuilder().parameters(context.getGlobalOperationParameters());
            context.operationBuilder().parameters(readParameters(context));
            return null;
        }
    
        private List<parameter> readParameters(final OperationContext context) {
    
            List<resolvedmethodparameter> methodParameters = context.getParameters();
            List<parameter> parameters = newArrayList();
    
            for (ResolvedMethodParameter methodParameter : methodParameters) {
                ResolvedType alternate = context.alternateFor(methodParameter.getParameterType());
                if (!shouldIgnore(methodParameter, alternate, context.getIgnorableParameterTypes())) {
    
                    ParameterContext parameterContext = new ParameterContext(methodParameter,
                            new ParameterBuilder(),
                            context.getDocumentationContext(),
                            context.getGenericsNamingStrategy(),
                            context);
    
                    if (shouldExpand(methodParameter, alternate)) {
                        parameters.addAll(
                                expander.expand(
                                        new ExpansionContext("", alternate, context)));
                    } else {
                        parameters.add(pluginsManager.parameter(parameterContext));
                    }
                }
            }
            return FluentIterable.from(parameters).filter(not(hiddenParams())).toList();
        }
    
    
        private Predicate<parameter> hiddenParams() {
            return new Predicate<parameter>() {
                @Override
                public boolean apply(Parameter input) {
                    return input.isHidden();
                }
            };
        }
    
        private boolean shouldIgnore(
                final ResolvedMethodParameter parameter,
                ResolvedType resolvedParameterType,
                final Set<class> ignorableParamTypes) {
    
            if (ignorableParamTypes.contains(resolvedParameterType.getErasedType())) {
                return true;
            }
            return FluentIterable.from(ignorableParamTypes)
                    .filter(isAnnotation())
                    .filter(parameterIsAnnotatedWithIt(parameter)).size() > 0;
    
        }
    
        private Predicate<class> parameterIsAnnotatedWithIt(final ResolvedMethodParameter parameter) {
            return new Predicate<class>() {
                @Override
                public boolean apply(Class input) {
                    return parameter.hasParameterAnnotation(input);
                }
            };
        }
    
        private Predicate<class> isAnnotation() {
            return new Predicate<class>() {
                @Override
                public boolean apply(Class input) {
                    return Annotation.class.isAssignableFrom(input);
                }
            };
        }
    
        private boolean shouldExpand(final ResolvedMethodParameter parameter, ResolvedType resolvedParamType) {
            return !parameter.hasParameterAnnotation(RequestBody.class)
                    && !parameter.hasParameterAnnotation(RequestPart.class)
                    && !parameter.hasParameterAnnotation(RequestParam.class)
                    && !parameter.hasParameterAnnotation(PathVariable.class)
                    && !isBaseType(typeNameFor(resolvedParamType.getErasedType()))
                    && !enumTypeDeterminer.isEnum(resolvedParamType.getErasedType())
                    && !isContainerType(resolvedParamType)
                    && !isMapType(resolvedParamType)
                    && !noExpandAnnotaion(parameter);
    
        }
    
        private boolean noExpandAnnotaion(ResolvedMethodParameter parameter) {
            log.info("开始决定是否展开问题");
            if (!parameter.hasParameterAnnotation(NoSwaggerExpand.class)) {
                return false;
            }
            NoSwaggerExpand noSwaggerExpand = (NoSwaggerExpand) parameter.getAnnotations().stream().filter(item -> item instanceof NoSwaggerExpand).findAny().orElse(null);
            if (noSwaggerExpand.expand()) {
                return false;
            }
            return true;
        }
    
    }
    

    最重要的是这里的修改

    image-20211011165542819

    这里加上对自定义注解修饰的入参进行了判定,使得被自定义注解修饰的入参可以被Swagger当做@RequestBody一样处理。

    2.Definition属性值填充的逻辑调整

    再定义一个切面

    import com.cosmoplat.qdind.swagger.annotation.NoSwaggerExpand;
    import com.fasterxml.classmate.ResolvedType;
    import com.fasterxml.classmate.TypeResolver;
    import lombok.extern.slf4j.Slf4j;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestPart;
    import springfox.documentation.service.ResolvedMethodParameter;
    import springfox.documentation.spi.DocumentationType;
    import springfox.documentation.spi.service.contexts.OperationContext;
    import springfox.documentation.spi.service.contexts.RequestMappingContext;
    import springfox.documentation.spring.web.readers.operation.OperationModelsProvider;
    
    import java.util.List;
    
    import static springfox.documentation.schema.ResolvedTypes.resolvedTypeSignature;
    
    /**
     * @author kdyzm
     * @date 2021/10/11
     */
    @Slf4j
    @Aspect
    @Component
    public class SwaggerDefinitionAspect {
    
        private static final Logger LOG = LoggerFactory.getLogger(OperationModelsProvider.class);
        private final TypeResolver typeResolver;
    
        @Autowired
        public SwaggerDefinitionAspect(TypeResolver typeResolver) {
            this.typeResolver = typeResolver;
        }
    
        
        @Around("execution(* springfox.documentation.spring.web.readers.operation.OperationModelsProvider.apply(..))")
        public Object pointCut(ProceedingJoinPoint point) throws Throwable {
            Object[] args = point.getArgs();
            RequestMappingContext context = (RequestMappingContext) args[0];
            collectFromReturnType(context);
            collectParameters(context);
            collectGlobalModels(context);
            return null;
        }
        
        private void collectGlobalModels(RequestMappingContext context) {
            for (ResolvedType each : context.getAdditionalModels()) {
                context.operationModelsBuilder().addInputParam(each);
                context.operationModelsBuilder().addReturn(each);
            }
        }
    
        private void collectFromReturnType(RequestMappingContext context) {
            ResolvedType modelType = context.getReturnType();
            modelType = context.alternateFor(modelType);
            LOG.debug("Adding return parameter of type {}", resolvedTypeSignature(modelType).or("<null>"));
            context.operationModelsBuilder().addReturn(modelType);
        }
    
        private void collectParameters(RequestMappingContext context) {
    
    
            LOG.debug("Reading parameters models for handlerMethod |{}|", context.getName());
    
            List<resolvedmethodparameter> parameterTypes = context.getParameters();
            for (ResolvedMethodParameter parameterType : parameterTypes) {
                if (parameterType.hasParameterAnnotation(RequestBody.class)
                        || parameterType.hasParameterAnnotation(RequestPart.class)
                || parameterType.hasParameterAnnotation(NoSwaggerExpand.class)
                ) {
                    ResolvedType modelType = context.alternateFor(parameterType.getParameterType());
                    LOG.debug("Adding input parameter of type {}", resolvedTypeSignature(modelType).or("<null>"));
                    context.operationModelsBuilder().addInputParam(modelType);
                }
            }
            LOG.debug("Finished reading parameters models for handlerMethod |{}|", context.getName());
        }
    }
    

    在这里只改动了一处代码,使得被自定义注解修饰的入参能够被添加到Definition属性中去。

    image-20211011165919011

    做完以上两步,即可修复springmvc独立的参数解析器功能和swagger功能冲突的问题。

  • 相关阅读:
    《Eric带您走近Office 2010》系列专题来啦!
    以人为本 体验至上(二)
    以人为本 体验至上(一)
    操作Static对象的多线程问题
    TroubleShoot:SilverLight中Wcf调用时出现Not Found等错误
    delegate与event的关系说明
    SharePoint对象模型性能考量
    SharePoint自带Silverlight WebPart中文输入问题处理
    关于Wcf事件多次执行的问题
    SharePoint中调试SilverLight程序
  • 原文地址:https://www.cnblogs.com/kuangdaoyizhimei/p/15394132.html
Copyright © 2020-2023  润新知