<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          解決swagger和自定義參數(shù)解析器的功能沖突

          共 10399字,需瀏覽 21分鐘

           ·

          2022-01-11 13:52


          源?/?? ? ? ??文/?

          前情提要

          我們之前提到過,@RequestBody使用的參數(shù)解析器RequestResponseBodyMethodProcessor優(yōu)先級高于我們自定義的參數(shù)解析器,所以為了正常使用,需要將@RequestBody?注解去掉。這就會導(dǎo)致swagger無法識別正確的參數(shù)類型,將請求體識別為Query Params,然后將body展開。
          可以看到,所有參數(shù)都被識別為ModelAttribute類型(query標(biāo)志),而我們所期待的正確格式應(yīng)當(dāng)是如下樣子
          因為該方式可以大大提高代碼的可讀性和可復(fù)用性,所以我們要知難而上,找出問題,解決問題!

          問題產(chǎn)生的原因

          產(chǎn)生這個問題的根本原因就是spring mvcswagger都對@RequestBody注解進(jìn)行了單獨的判定,功能上都依賴于該注解本身。

          springmvc@RequestBody注解的依賴

          就拿當(dāng)前自定義的參數(shù)解析器來說,如果對請求參數(shù)加上了?@RequestBody?注解,對參數(shù)的反序列化會提前被RequestResponseBodyMethodProcessor攔截,自定義的參數(shù)解析器會失效。
          具體源代碼位置: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
          可以看到,該參數(shù)解析器對加上@ReuqestBody注解的參數(shù)都支持解析,然后做序列化的操作。然而它在參數(shù)解析器列表中的優(yōu)先級比較高,自定義的參數(shù)解析器添加到參數(shù)解析器列表之后會排在它的后面,所以如果加上@RequestBody注解,自定義的參數(shù)解析器就失效了。
          因此使用自定義參數(shù)解析器一定不能使用@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
          此案例中用到的自定義參數(shù)解析器為HdxArgumentResolver

          swagger@Requestbody的依賴

          經(jīng)過調(diào)用棧追蹤,最終發(fā)現(xiàn)在兩個地方的功能會對@RequestBody注解有單獨判定!(感興趣的可以自行追蹤??)
          • 請求類型判定:也就是說POST請求類型是哪種類型,這決定了入?yún)⑹欠駮鳛?/span>Request Parameter被展開參數(shù),也就是文中的第一張圖,整個model都被視為ModelAttribute展開了。
          • Definition屬性值填充:這確保被@RequestBody注解修飾的入?yún)徽o@示,如文中第二張圖片所示。

          請求類型判定

          源代碼位置: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等常用注解進(jìn)行了單獨的判定,確保這些注解修飾的入?yún)⒉粫蛔鳛?/span>RequestParam展開。

          Definition屬性值填充

          Definition屬性中填充了入?yún)ⅰ⒊鰠⒌葏?shù)類型,如果沒有相應(yīng)的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
          可以看到,只有被RequestBody注解和RequestPart注解修飾的入?yún)⒉艜唤邮者M(jìn)入Definition屬性。
          綜合以上兩張圖的源代碼分析,可以看到,swagger功能依賴于@RequestBody注解,入?yún)⑷绻槐辉撟⒔庑揎棧瑒tswagger功能就會不完整,這和在springmvc中使用獨立的參數(shù)解析器功能不得使用@RequestBody注解矛盾。

          解決問題

          從以上分析可以得到結(jié)論,這里的根本問題是springmvc中獨立的參數(shù)解析器功能和swagger功能上的沖突,一個要求不能加上@RequestBody注解,一個要求必須加上@RequestBody注解,所以解決方法上可以使用兩種方式
          • springmvc入手,想辦法提高自定義參數(shù)解析器的優(yōu)先級,只要自定義的參數(shù)解析器優(yōu)先級比RequestResponseBodyMethodProcessor高,則就可以在自定義的參數(shù)上加上@RequestBody注解,swagger功能自然而然就能正常了。
          • swagger入手,想辦法解決掉上面兩部分對@RequestBody的單獨判定,不修改springmvc相關(guān)功能也可以讓swagger功能正常。
          考慮到修改springmvc功能可能會對以后的版本升級造成較大影響,這里決定利用切面修改原有的swagger@RequestBody的兩個地方的行為,從而讓swagger功能正常。

          請求類型判定的邏輯調(diào)整

          首先,定義一個注解
          @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;
          }
          將其加到入?yún)⑸?/span>
          ????@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?readParameters(final?OperationContext?context)?{

          ????????List?methodParameters?=?context.getParameters();
          ????????List?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?hiddenParams()?{
          ????????return?new?Predicate()?{
          ????????????@Override
          ????????????public?boolean?apply(Parameter?input)?{
          ????????????????return?input.isHidden();
          ????????????}
          ????????};
          ????}

          ????private?boolean?shouldIgnore(
          ????????????final?ResolvedMethodParameter?parameter,
          ????????????ResolvedType?resolvedParameterType,
          ????????????final?Set?ignorableParamTypes)
          ?
          {

          ????????if?(ignorableParamTypes.contains(resolvedParameterType.getErasedType()))?{
          ????????????return?true;
          ????????}
          ????????return?FluentIterable.from(ignorableParamTypes)
          ????????????????.filter(isAnnotation())
          ????????????????.filter(parameterIsAnnotatedWithIt(parameter)).size()?>?0;

          ????}

          ????private?Predicate?parameterIsAnnotatedWithIt(final?ResolvedMethodParameter?parameter)?{
          ????????return?new?Predicate<class>()?{
          ????????????@Override
          ????????????public?boolean?apply(Class?input)?{
          ????????????????return?parameter.hasParameterAnnotation(input);
          ????????????}
          ????????};
          ????}

          ????private?Predicate?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;
          ????}

          }
          最重要的是這里的修改
          這里加上對自定義注解修飾的入?yún)⑦M(jìn)行了判定,使得被自定義注解修飾的入?yún)⒖梢员?/span>Swagger當(dāng)做@RequestBody一樣處理。

          Definition屬性值填充的邏輯調(diào)整

          再定義一個切面
          @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(""));
          ????????context.operationModelsBuilder().addReturn(modelType);
          ????}

          ????private?void?collectParameters(RequestMappingContext?context)?{


          ????????LOG.debug("Reading?parameters?models?for?handlerMethod?|{}|",?context.getName());

          ????????List?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(""));
          ????????????????context.operationModelsBuilder().addInputParam(modelType);
          ????????????}
          ????????}
          ????????LOG.debug("Finished?reading?parameters?models?for?handlerMethod?|{}|",?context.getName());
          ????}
          }
          在這里只改動了一處代碼,使得被自定義注解修飾的入?yún)⒛軌虮惶砑拥?/span>Definition屬性中去。
          做完以上兩步,即可修復(fù)springmvc獨立的參數(shù)解析器功能和swagger功能沖突的問題。
          以上就是今天的全部內(nèi)容了,如果你有不同的意見或者更好的idea,歡迎聯(lián)系阿Q,添加阿Q可以加入技術(shù)交流群參與討論呦!

          END


          最后給大家來個福利:



          一鍵三連「分享」、「點贊」和「在看」


          瀏覽 56
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  久久无码专区 | 草比视频网 | 新中文字幕亚洲 | 黄页网站视频 | 中文娱乐在线视频 |