服务器之家:专注于服务器技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - Java教程 - 实战篇:解决 Swagger 和自定义参数解析器的功能冲突

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

2022-01-06 20:58阿Q说代码kdyzm Java教程

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

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

前情提要

看了上一篇文章看了同事写的代码,我竟然开始默默的模仿了。。。的小伙伴,应该已经对使用参数解析器来完成第三方接口的统一验签有了清晰的认识。

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

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

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

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

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

问题产生的原因

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

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

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

可以看到,该参数解析器对加上@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

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

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

swagger对@Requestbody的依赖

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

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

请求类型判定

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

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

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

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

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

可以看到,只有被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(RetentionPolicy.RUNTIME) 
  3. @Target({ElementType.PARAMETER}) 
  4. public @interface NoSwaggerExpand { 
  5.  
  6.     /** 
  7.      * default swagger expand disable 
  8.      * @see OperationParameterReader#shouldExpand(springfox.documentation.service.ResolvedMethodParameter, com.fasterxml.classmate.ResolvedType) 
  9.      */ 
  10.     boolean expand() default false

将其加到入参上

  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.  
  6.     private final ModelAttributeParameterExpander expander; 
  7.     private final EnumTypeDeterminer enumTypeDeterminer; 
  8.  
  9.     @Autowired 
  10.     private DocumentationPluginsManager pluginsManager; 
  11.  
  12.     @Autowired 
  13.     public SwaggerExpandAspect( 
  14.             ModelAttributeParameterExpander expander, 
  15.             EnumTypeDeterminer enumTypeDeterminer) { 
  16.         this.expander = expander; 
  17.         this.enumTypeDeterminer = enumTypeDeterminer; 
  18.     } 
  19.  
  20.     @Around("execution(* springfox.documentation.spring.web.readers.operation.OperationParameterReader.apply(..))"
  21.     public Object pointCut(ProceedingJoinPoint point) throws Throwable { 
  22.         Object[] args = point.getArgs(); 
  23.         OperationContext context = (OperationContext) args[0]; 
  24.         context.operationBuilder().parameters(context.getGlobalOperationParameters()); 
  25.         context.operationBuilder().parameters(readParameters(context)); 
  26.         return null
  27.     } 
  28.  
  29.     private List<parameter> readParameters(final OperationContext context) { 
  30.  
  31.         List<resolvedmethodparameter> methodParameters = context.getParameters(); 
  32.         List<parameter> parameters = newArrayList(); 
  33.  
  34.         for (ResolvedMethodParameter methodParameter : methodParameters) { 
  35.             ResolvedType alternate = context.alternateFor(methodParameter.getParameterType()); 
  36.             if (!shouldIgnore(methodParameter, alternate, context.getIgnorableParameterTypes())) { 
  37.  
  38.                 ParameterContext parameterContext = new ParameterContext(methodParameter, 
  39.                         new ParameterBuilder(), 
  40.                         context.getDocumentationContext(), 
  41.                         context.getGenericsNamingStrategy(), 
  42.                         context); 
  43.  
  44.                 if (shouldExpand(methodParameter, alternate)) { 
  45.                     parameters.addAll( 
  46.                             expander.expand( 
  47.                                     new ExpansionContext("", alternate, context))); 
  48.                 } else { 
  49.                     parameters.add(pluginsManager.parameter(parameterContext)); 
  50.                 } 
  51.             } 
  52.         } 
  53.         return FluentIterable.from(parameters).filter(not(hiddenParams())).toList(); 
  54.     } 
  55.  
  56.  
  57.     private Predicate<parameter> hiddenParams() { 
  58.         return new Predicate<parameter>() { 
  59.             @Override 
  60.             public boolean apply(Parameter input) { 
  61.                 return input.isHidden(); 
  62.             } 
  63.         }; 
  64.     } 
  65.  
  66.     private boolean shouldIgnore( 
  67.             final ResolvedMethodParameter parameter, 
  68.             ResolvedType resolvedParameterType, 
  69.             final Set<class> ignorableParamTypes) { 
  70.  
  71.         if (ignorableParamTypes.contains(resolvedParameterType.getErasedType())) { 
  72.             return true
  73.         } 
  74.         return FluentIterable.from(ignorableParamTypes) 
  75.                 .filter(isAnnotation()) 
  76.                 .filter(parameterIsAnnotatedWithIt(parameter)).size() > 0; 
  77.  
  78.     } 
  79.  
  80.     private Predicate<class> parameterIsAnnotatedWithIt(final ResolvedMethodParameter parameter) { 
  81.         return new Predicate<class>() { 
  82.             @Override 
  83.             public boolean apply(Class input) { 
  84.                 return parameter.hasParameterAnnotation(input); 
  85.             } 
  86.         }; 
  87.     } 
  88.  
  89.     private Predicate<class> isAnnotation() { 
  90.         return new Predicate<class>() { 
  91.             @Override 
  92.             public boolean apply(Class input) { 
  93.                 return Annotation.class.isAssignableFrom(input); 
  94.             } 
  95.         }; 
  96.     } 
  97.  
  98.     private boolean shouldExpand(final ResolvedMethodParameter parameter, ResolvedType resolvedParamType) { 
  99.         return !parameter.hasParameterAnnotation(RequestBody.class) 
  100.                 && !parameter.hasParameterAnnotation(RequestPart.class) 
  101.                 && !parameter.hasParameterAnnotation(RequestParam.class) 
  102.                 && !parameter.hasParameterAnnotation(PathVariable.class) 
  103.                 && !isBaseType(typeNameFor(resolvedParamType.getErasedType())) 
  104.                 && !enumTypeDeterminer.isEnum(resolvedParamType.getErasedType()) 
  105.                 && !isContainerType(resolvedParamType) 
  106.                 && !isMapType(resolvedParamType) 
  107.                 && !noExpandAnnotaion(parameter); 
  108.  
  109.     } 
  110.  
  111.     private boolean noExpandAnnotaion(ResolvedMethodParameter parameter) { 
  112.         log.info("开始决定是否展开问题"); 
  113.         if (!parameter.hasParameterAnnotation(NoSwaggerExpand.class)) { 
  114.             return false
  115.         } 
  116.         NoSwaggerExpand noSwaggerExpand = (NoSwaggerExpand) parameter.getAnnotations().stream().filter(item -> item instanceof NoSwaggerExpand).findAny().orElse(null); 
  117.         if (noSwaggerExpand.expand()) { 
  118.             return false
  119.         } 
  120.         return true
  121.     } 
  122.  

最重要的是这里的修改

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

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

Definition属性值填充的逻辑调整

再定义一个切面

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

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

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

原文链接:https://mp.weixin.qq.com/s/1DySbQnacMFzaOxzUJFnjw

延伸 · 阅读

精彩推荐
  • Java教程springboot+thymeleaf国际化之LocaleResolver接口的示例

    springboot+thymeleaf国际化之LocaleResolver接口的示例

    本篇文章主要介绍了springboot+thymeleaf国际化之LocaleResolver的示例 ,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧...

    幽魂步5442021-02-01
  • Java教程Java枚举的使用方法详解

    Java枚举的使用方法详解

    这篇文章主要介绍了 Java枚举的使用方法详解的相关资料,希望通过本文大家能掌握枚举的使用方法,需要的朋友可以参考下...

    SexyCode7772020-12-30
  • Java教程java15新功能的详细讲解

    java15新功能的详细讲解

    这篇文章主要介绍了java15的新功能,虽然java15并不是长期支持的版本,但是很多新功能还是很有用的。感兴趣的小伙伴可以参考一下...

    程序猿阿朗7962021-11-21
  • Java教程使用GSON库转换Java对象为JSON对象的进阶实例详解

    使用GSON库转换Java对象为JSON对象的进阶实例详解

    这篇文章主要介绍了使用GSON库转换Java对象为JSON对象的进阶实例详解,包括注册TypeAdapter及处理Enum类型等实际运用中可能遇到的一些复杂问题,需要的朋友可...

    李坤3432020-05-20
  • Java教程Java实现选择排序

    Java实现选择排序

    这篇文章主要介绍了Java实现选择排序,把一列数组按从小到大或从大到小排序,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可...

    这个对数不太对3852021-12-07
  • Java教程浅谈JavaWeb中的web.xml配置部署描述符文件

    浅谈JavaWeb中的web.xml配置部署描述符文件

    下面小编就为大家带来一篇浅谈JavaWeb中的web.xml配置部署描述符文件。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看...

    Java之家3512020-09-28
  • Java教程java 设计模式(DAO)的实例详解

    java 设计模式(DAO)的实例详解

    这篇文章主要介绍了java 设计模式(DAO)的实例详解的相关资料,希望通过本文能帮助到大家,需要的朋友可以参考下...

    海那边的小萌男8732021-01-07
  • Java教程spring boot 使用utf8mb4的操作

    spring boot 使用utf8mb4的操作

    这篇文章主要介绍了spring boot 使用utf8mb4的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    toalaska6302021-08-10