哈喽大家好,我是阿Q!点星标不迷路😃
前情提要
看了上一篇文章看了同事写的代码,我竟然开始默默的模仿了。。。的小伙伴,应该已经对使用参数解析器来完成第三方接口的统一验签有了清晰的认识。
我们在上文中提到过,@RequestBody
使用的参数解析器RequestResponseBodyMethodProcessor
优先级高于我们自定义的参数解析器,所以为了正常使用,需要将@RequestBody
注解去掉。这就会导致swagger
无法识别正确的参数类型,将请求体识别为Query Params
,然后将body
展开。
可以看到,所有参数都被识别为ModelAttribute
类型(query
标志),而我们所期待的正确格式应当是如下样子
因为该方式可以大大提高代码的可读性和可复用性,所以我们要知难而上,找出问题,解决问题!
问题产生的原因
产生这个问题的根本原因就是spring mvc
和swagger
都对@RequestBody
注解进行了单独的判定,功能上都依赖于该注解本身。
springmvc
对@RequestBody
注解的依赖
就拿当前自定义的参数解析器来说,如果对请求参数加上了 @RequestBody
注解,对参数的反序列化会提前被RequestResponseBodyMethodProcessor
拦截,自定义的参数解析器会失效。
可以看到,该参数解析器对加上@ReuqestBody
注解的参数都支持解析,然后做序列化的操作。然而它在参数解析器列表中的优先级比较高,自定义的参数解析器添加到参数解析器列表之后会排在它的后面,所以如果加上@RequestBody
注解,自定义的参数解析器就失效了。
因此使用自定义参数解析器一定不能使用@RequestBody
注解
此案例中用到的自定义参数解析器为
HdxArgumentResolver
swagger
对@Requestbody
的依赖
经过调用栈追踪,最终发现在两个地方的功能会对@RequestBody
注解有单独判定!(感兴趣的可以自行追踪😃)
- 请求类型判定:也就是说
POST
请求类型是哪种类型,这决定了入参是否会作为Request Parameter
被展开参数,也就是文中的第一张图,整个model
都被视为ModelAttribute
展开了。 Definition
属性值填充:这确保被@RequestBody
注解修饰的入参会被正常显示,如文中第二张图片所示。
请求类型判定
这里对RequestBody
等常用注解进行了单独的判定,确保这些注解修饰的入参不会被作为RequestParam
展开。
Definition
属性值填充
Definition
属性中填充了入参、出参等参数类型,如果没有相应的Model
定义,则swagger
信息就会是不完整的,在浏览器页面中的显示也会是不全的。填充Definition
的逻辑也依赖于@RequestBody
注解。
可以看到,只有被RequestBody
注解和RequestPart
注解修饰的入参才会被接收进入Definition
属性。
综合以上两张图的源代码分析,可以看到,swagger
功能依赖于@RequestBody
注解,入参如果不被该注解修饰,则swagger
功能就会不完整,这和在springmvc
中使用独立的参数解析器功能不得使用@RequestBody
注解矛盾。
解决问题
从以上分析可以得到结论,这里的根本问题是springmvc
中独立的参数解析器功能和swagger
功能上的冲突,一个要求不能加上@RequestBody
注解,一个要求必须加上@RequestBody
注解,所以解决方法上可以使用两种方式
- 从
springmvc
入手,想办法提高自定义参数解析器的优先级,只要自定义的参数解析器优先级比RequestResponseBodyMethodProcessor
高,则就可以在自定义的参数上加上@RequestBody
注解,swagger
功能自然而然就能正常了。 - 从
swagger
入手,想办法解决掉上面两部分对@RequestBody
的单独判定,不修改springmvc
相关功能也可以让swagger
功能正常。
考虑到修改springmvc
功能可能会对以后的版本升级造成较大影响,这里决定利用切面修改原有的swagger
对@RequestBody
的两个地方的行为,从而让swagger
功能正常。
请求类型判定的逻辑调整
首先,定义一个注解
@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; }
然后定义切面
@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; } }
最重要的是这里的修改
这里加上对自定义注解修饰的入参进行了判定,使得被自定义注解修饰的入参可以被Swagger
当做@RequestBody
一样处理。
Definition属性值填充的逻辑调整
再定义一个切面
@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
属性中去。
做完以上两步,即可修复springmvc
独立的参数解析器功能和swagger
功能冲突的问题。
以上就是今天的全部内容了,如果你有不同的意见或者更好的idea
,欢迎联系阿Q,添加阿Q可以加入技术交流群参与讨论呦!