实战篇:解决swagger和自定义参数解析器的功能冲突

时间:2024-10-12 21:35:29

前言

@RequestBody使用的参数解析器RequestResponseBodyMethodProcessor优先级高于我们自定义的参数解析器,所以为了正常使用,需要将@RequestBody 注解去掉。这就会导致swagger无法识别正确的参数类型,将请求体识别为Query Params,然后将body展开。

图片

 

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

图片

因为该方式可以大大提高代码的可读性和可复用性,所以我们要知难而上,找出问题,解决问题!

问题产生的原因

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

springmvc@RequestBody注解的依赖

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

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

图片

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

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

下图源代码位置:/spring-projects/spring-framework/blob/5./spring-web/src/main/java/org/springframework/web/method/support/#L129

图片

此案例中用到的自定义参数解析器为HdxArgumentResolver

swagger@Requestbody的依赖

经过调用栈追踪,最终发现在两个地方的功能会对@RequestBody注解有单独判定!(感兴趣的可以自行追踪????)

  • 请求类型判定:也就是说POST请求类型是哪种类型,这决定了入参是否会作为Request Parameter被展开参数,也就是文中的第一张图,整个model都被视为ModelAttribute展开了。

  • Definition属性值填充:这确保被@RequestBody注解修饰的入参会被正常显示,如文中第二张图片所示。

请求类型判定

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

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

Definition属性值填充

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

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

图片

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

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

解决问题

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

  • springmvc入手,想办法提高自定义参数解析器的优先级,只要自定义的参数解析器优先级比RequestResponseBodyMethodProcessor高,则就可以在自定义的参数上加上@RequestBody注解,swagger功能自然而然就能正常了。

  • swagger入手,想办法解决掉上面两部分对@RequestBody的单独判定,不修改springmvc相关功能也可以让swagger功能正常。

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

请求类型判定的逻辑调整

首先,定义一个注解

  1. @Documented
  2. @Retention()
  3. @Target({})
  4. public @interface NoSwaggerExpand {
  5.     /**
  6.      * default swagger expand disable
  7.      * @see OperationParameterReader#shouldExpand(, )
  8.      */
  9.     boolean expand() default false;
  10. }

将其加到入参上

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

然后定义切面

  1. @Slf4j
  2. @Aspect
  3. @Component
  4. public class SwaggerExpandAspect {
  5.     private final ModelAttributeParameterExpander expander;
  6.     private final EnumTypeDeterminer enumTypeDeterminer;
  7.     @Autowired
  8.     private DocumentationPluginsManager pluginsManager;
  9.     @Autowired
  10.     public SwaggerExpandAspect(
  11.             ModelAttributeParameterExpander expander,
  12.             EnumTypeDeterminer enumTypeDeterminer) {
  13.          = expander;
  14.          = enumTypeDeterminer;
  15.     }
  16.     @Around("execution(* (..))")
  17.     public Object pointCut(ProceedingJoinPoint point) throws Throwable {
  18.         Object[] args = ();
  19.         OperationContext context = (OperationContext) args[0];
  20.         ().parameters(());
  21.         ().parameters(readParameters(context));
  22.         return null;
  23.     }
  24.     private List<parameter> readParameters(final OperationContext context) {
  25.         List<resolvedmethodparameter> methodParameters = ();
  26.         List<parameter> parameters = newArrayList();
  27.         for (ResolvedMethodParameter methodParameter : methodParameters) {
  28.             ResolvedType alternate = (());
  29.             if (!shouldIgnore(methodParameter, alternate, ())) {
  30.                 ParameterContext parameterContext = new ParameterContext(methodParameter,
  31.                         new ParameterBuilder(),
  32.                         (),
  33.                         (),
  34.                         context);
  35.                 if (shouldExpand(methodParameter, alternate)) {
  36.                     (
  37.                             (
  38.                                     new ExpansionContext(""alternate, context)));
  39.                 } else {
  40.                     parameters.add((parameterContext));
  41.                 }
  42.             }
  43.         }
  44.         return FluentIterable.from(parameters).filter(not(hiddenParams())).toList();
  45.     }
  46.     private Predicate<parameter> hiddenParams() {
  47.         return new Predicate<parameter>() {
  48.             @Override
  49.             public boolean apply(Parameter input) {
  50.                 return input.isHidden();
  51.             }
  52.         };
  53.     }
  54.     private boolean shouldIgnore(
  55.             final ResolvedMethodParameter parameter,
  56.             ResolvedType resolvedParameterType,
  57.             final Set<class> ignorableParamTypes) {
  58.         if (ignorableParamTypes.contains(())) {
  59.             return true;
  60.         }
  61.         return FluentIterable.from(ignorableParamTypes)
  62.                 .filter(isAnnotation())
  63.                 .filter(parameterIsAnnotatedWithIt(parameter)).size() > 0;
  64.     }
  65.     private Predicate<class> parameterIsAnnotatedWithIt(final ResolvedMethodParameter parameter) {
  66.         return new Predicate<class>() {
  67.             @Override
  68.             public boolean apply(Class input) {
  69.                 return (input);
  70.             }
  71.         };
  72.     }
  73.     private Predicate<class> isAnnotation() {
  74.         return new Predicate<class>() {
  75.             @Override
  76.             public boolean apply(Class input) {
  77.                 return Annotation.class.isAssignableFrom(input);
  78.             }
  79.         };
  80.     }
  81.     private boolean shouldExpand(final ResolvedMethodParameter parameter, ResolvedType resolvedParamType) {
  82.         return !(RequestBody.class)
  83.                 && !(RequestPart.class)
  84.                 && !(RequestParam.class)
  85.                 && !(PathVariable.class)
  86.                 && !isBaseType(typeNameFor(()))
  87.                 && !(())
  88.                 && !isContainerType(resolvedParamType)
  89.                 && !isMapType(resolvedParamType)
  90.                 && !noExpandAnnotaion(parameter);
  91.     }
  92.     private boolean noExpandAnnotaion(ResolvedMethodParameter parameter) {
  93.         ("开始决定是否展开问题");
  94.         if (!(NoSwaggerExpand.class)) {
  95.             return false;
  96.         }
  97.         NoSwaggerExpand noSwaggerExpand = (NoSwaggerExpand) ().stream().filter(item -> item instanceof NoSwaggerExpand).findAny().orElse(null);
  98.         if (()) {
  99.             return false;
  100.         }
  101.         return true;
  102.     }
  103. }

最重要的是这里的修改

图片

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

Definition属性值填充的逻辑调整

再定义一个切面

  1. @Slf4j
  2. @Aspect
  3. @Component
  4. public class SwaggerDefinitionAspect {
  5.     private static final Logger LOG = ();
  6.     private final TypeResolver typeResolver;
  7.     @Autowired
  8.     public SwaggerDefinitionAspect(TypeResolver typeResolver) {
  9.         this.typeResolver = typeResolver;
  10.     }
  11.     
  12.     @Around("execution(* (..))")
  13.     public Object pointCut(ProceedingJoinPoint point) throws Throwable {
  14.         Object[] args = ();
  15.         RequestMappingContext context = (RequestMappingContext) args[0];
  16.         collectFromReturnType(context);
  17.         collectParameters(context);
  18.         collectGlobalModels(context);
  19.         return null;
  20.     }
  21.     
  22.     private void collectGlobalModels(RequestMappingContext context) {
  23.         for (ResolvedType each : ()) {
  24.             ().addInputParam(each);
  25.             ().addReturn(each);
  26.         }
  27.     }
  28.     private void collectFromReturnType(RequestMappingContext context) {
  29.         ResolvedType modelType = ();
  30.         modelType = (modelType);
  31.         ("Adding return parameter of type {}", resolvedTypeSignature(modelType).or("<null>"));
  32.         ().addReturn(modelType);
  33.     }
  34.     private void collectParameters(RequestMappingContext context) {
  35.         ("Reading parameters models for handlerMethod |{}|", ());
  36.         List<resolvedmethodparameter> parameterTypes = ();
  37.         for (ResolvedMethodParameter parameterType : parameterTypes) {
  38.             if (()
  39.                     || ()
  40.             || ()
  41.             ) {
  42.                 ResolvedType modelType = (());
  43.                 ("Adding input parameter of type {}", resolvedTypeSignature(modelType).or("<null>"));
  44.                 ().addInputParam(modelType);
  45.             }
  46.         }
  47.         ("Finished reading parameters models for handlerMethod |{}|", ());
  48.     }
  49. }

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

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

原文地址:实战篇:解决swagger和自定义参数解析器的功能冲突

如果觉得本文对你有帮助,麻烦点赞转发加关注支持一下