当前位置: 首页 > 科技观察

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

时间:2023-03-23 01:27:00 科技观察

实用篇:解决Swagger与自定义参数解析器的功能冲突。.小伙伴们,对于使用参数解析器来完成第三方接口的统一校验,你应该有一个清晰的认识。正如我们上面提到的,@RequestBody使用的参数解析器RequestResponseBodyMethodProcessor的优先级高于我们自定义的参数解析器,所以为了正常使用,需要去掉@RequestBody注解。这会导致swagger无法识别正确的参数类型,将requestbody识别为QueryParams,然后展开body。可以看出,所有的参数都被识别为ModelAttribute类型(查询标志),而我们期望的正确格式应该是这样的因为这种方式可以大大提高代码的可读性和复用性,所以要知道难度另一方面,找出问题,解决问题!问题的根本原因是springmvc和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可以看到参数解析器支持对@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本例中使用的自定义参数解析器是HdxArgumentResolverswagger对@Requestbody的依赖调用stacktrace后终于发现两个地方的函数会对@RequestBody注解有单独的判断!(有兴趣的可以自己追踪一下?)请求类型判断:也就是说POST请求类型是什么类型,决定了传入的参数是否会展开为RequestParameter,也就是第一个text在一张图片中,整个模型被视为ModelAttribute展开。定义属性值填充:这样可以保证@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这里对RequestBody等常见的注解进行单独判断,保证这些注解修改的入参不会扩展为RequestParam。定义属性值填充定义属性填充输入参数、输出参数等参数类型。如果没有相应的Model定义,swagger信息就会不完整,在浏览器页面上的展示也会不完整。填充定义的逻辑也取决于@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可以看到只有RequestBody注解和RequestPart注解修改的入参才会被接受到Definition属性中。结合上两图的源码分析,可以看出swagger函数依赖于@RequestBody注解。如果输入参数没有被这个注解修饰,swagger函数就会不完整,这类似于springmvc中使用独立的参数解析器,函数一定不能和@RequestBody注解相矛盾。解决问题从上面的分析可以得出,这里的根本问题是springmvc中独立参数解析器函数和swagger函数的冲突。一个需求不能加@RequestBody注解,一个需求必须加@RequestBody注解,所以解决方法有两种,从springmvc入手,尽量提高自定义参数解析器的优先级。只要自定义参数解析器的优先级高于RequestResponseBodyMethodProcessor,就可以在自定义参数上加上@RequestBody注解。swagger功能自然会正常工作。从swagger入手,想办法解决上面两部分@RequestBody的单独判断,在不修改springmvc相关函数的情况下让swagger功能正常。考虑到修改springmvc功能可能对以后的版本升级影响很大,所以决定在@RequestBody两处用aspects修改原来swagger的行为,使swagger功能正常。请求类型判断的逻辑调整首先定义一个注解@Documented@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.PARAMETER})public@interfaceNoSwaggerExpand{/***defaultswaggerexpanddisable*@seeOperationParameterReader#shouldExpand(springfox.documentation.service.ResolvedMethodParameter,com.fasterxml.classmate.ResolvedType)*/booleanexpand()defaultfalse;}添加到入口@ApiOperation(value="demo",notes="demo")@PostMapping(value="/test")publicResult<布尔>测试(@HdxDecrypt@NoSwaggerExpand@ApiParam(required=true)ReqDTOreqDTO){try{log.info(ObjectMapperFactory.getObjectMapper().writeValueAsString(reqDTO));}catch(JsonProcessingExceptione){log.error("",e);}returnnull;}然后定义切面@Slf4j@Aspect@ComponentpublicclassSwaggerExpandAspect{privatefinalModelAttributeParameterExpanderexpander;privatefinalEnumTypeDeterminerenumTypeDeterminer;@AutowiredprivateDocumentationPluginsManagerpluginsManager;@AutowiredpublicSwaggerExpandAspectParme(ModelEnumEnumTypeDeterminerenumTypeDeterminerxpanderexpander,EnumTypeDeterminerenumTypeDeterminer){this.expander=expander;this.enumTypeDeterminer=enumTypeDeterminer;}@Around("执行(*springfox.documentation.spring.web.readers.operation.OperationParameterReader.apply(..))")publicObjectpointCut(P??roceedingJoinPointpoint)throwsThrowable{Object[]args=point.getArgs();OperationContextcontext=(OperationContext)args[0];context.operationBuilder().parameters(context.getGlobalOperationParameters());context.operationBuilder().parameters(readParameters(上下文));returnnull;}privateListreadParameters(finalOperationContextcontext){ListmethodParameters=context.getParameters();Listparameters=newArrayList();for(ResolvedMethodParametermethodParameter:methodParameters){ResolvedTypealternate=context.alternateFor(methodParameter.getParameterType());if(!shouldIgnore(methodParameter,alternate,context.getIgnorableParameterTypes())){ParameterContextparameterContext=newParameterContext(methodParameter,newParameterBuilder(),context.getDocumentationContext(),context.getGenericsNamingStrategy(),context);if(shouldExpand(methodParameter,alternate)){parameters.addAll(expander.expand(newExpansionContext("",alternate,context)));}else{parameters.add(pluginsManager.parameter(parameterContext));}}}returnFluentIterable.from(parameters).filter(not(hiddenParams())).toList();}privatePredicatehiddenParams(){returnnewPredicate(){@Overridepublicbooleanapply(Parameterinput){returninput.isHidden();}};}privatebooleanshouldIgnore(finalResolvedMethodParameter,ResolvedTyperesolvedParameterType,finalSetignorableParamTypes){if(ignorableParamTypes.contains(resolvedParameterType.getErasedType())){returntrue;}returnFluentIterable.from(ignorableParamTypes).filter(isAnnotation()).filter(parameterIsAnnotatedWithIt(参数)).size()>0;}privatePredicate参数IsAnnotatedWithIt(finalResolvedMethodParameterparameter){returnnewPredicate(){@Overridepublicbooleanapply(Classinput){returnparameter.hasParameterAnnotation(input);}};}privatePredicateisAnnotation(){returnnewPredicate(){@Overridepublicbooleanapply(Classinput){returnAnnotation.class.isAssignableFrom(input);}};}privatebooleanshouldExpand(finalResolvedMethodParameterparameter,ResolvedTyperesolvedParamType){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))&&!没有ExpandAnnotaion(parameter);}privatebooleannoExpandAnnotaion(ResolvedMethodParameterparameter){log.info("开始决定是否展开问题");if(!parameter.hasParameterAnnotation(NoSwaggerExpand.class)){returnfalse;}NoSwaggerExpandnoSwaggerExpand=(NoSwaggerExpand)parameter.getAnnotations(.stream().filter(item->iteminstanceofNoSwaggerExpand).findAny().orElse(null);if(noSwaggerExpand.expand()){returnfalse;}returntrue;}}这里最重要的是这里的修改加上对self定义注解修饰的入参进行判断,这样自定义注解修饰的入参就可以被Swagger当作@RequestBodyDefinition属性值填充的逻辑调整再定义一个切面@Slf4j@Aspect@ComponentpublicclassSwaggerDefinitionAspect{privatestaticfinalLoggerLOG=LoggerFactory.getLogger(OperationModelsProvider.class);privatefinalTypeResolvertypeResolver;@AutowiredpublicSwaggerDefinitionAspect(TypeResolvertypeResolver){this.typeResolver=typeResolver;}@Around("execution(*springfox.documentation.spring.web.readers.operation.OperationModelsProvider.apply(..))")publicObjectpointCut(P??roceedingJoinPointpoint)throwsThrowable{Object[]args=point.getArgs();RequestMappingContextcontext=(RequestMappingContext)args[0];collectFromReturnType(上下文);collectParameters(上下文);collectGlobalModels(上下文);returnull;}privatevoidcollectGlobalModels(RequestMappingContextcontext){for(ResolvedTypeeach:context.getAdditionalModels()){context.operationModelsBuilder().addInputParam(each);context.operationModelsBuilder().addReturn(each);}}privatevoidcollectFromReturnType(RequestMappingContextcontext){已解决TypemodelType=context.getReturnType();modelType=context.alternateFor(modelType);LOG.debug("Addingreturnparameteroftype{}",resolvedTypeSignature(modelType).or(""));context.operationModelsBuilder().addReturn(modelType);}privatevoidcollectParameters(RequestMappingContextcontext){LOG.debug("ReadingparametersmodelsforhandlerMethod|{}|",context.getName());列表parameterTypes=context.getParameters();for(ResolvedMethodParameterparameterType:parameterTypes){if(parameterType.hasParameterAnnotation(RequestBody.class)||parameterType.hasParameterAnnotation(RequestPart.class)||parameterType.hasParameterAnnotation(NoSwaggerExpand.class)){ResolvedTypemodelType=context.alternateFor(parameterType.getParameterType());LOG.debug("Addinginputparameteroftype{}",resolvedTypeSignature(modelType).or(""));context.operationModelsBuilder().addInputParam(modelType);}}LOG.debug("FinishedreadingparametersmodelsforhandlerMethod|{}|",context.getName());}}这里只改了一段代码,让自定义注解修改的输入参数添加到Definition属性中即可完成以上两步,你可以修复springmvc的独立参数解析器函数和swagger函数的冲突。