<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>

          看了同事寫的代碼,我竟然開始默默的模仿了。。。

          共 5670字,需瀏覽 12分鐘

           ·

          2021-12-02 11:01


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

          背景

          事情是這樣的,目前我正在參與 XXXX 項目的搭建,需要與第三方對接接口。在對方的接口中存在幾個異步通知,為了接口的安全性,需要對接口的參數(shù)進行驗簽處理。

          為了方便大家對異步通知返回參數(shù)的處理,Z 同事提出要將該驗簽功能進行統(tǒng)一封裝,到時候大家只需要關注自己的業(yè)務邏輯即可。

          Z同事的解決方案

          Z 同事選擇的是“自定義參數(shù)解析器”的解決方案,接下來我們通過代碼來了解一下。

          自定義注解

          @Documented
          @Retention(RetentionPolicy.RUNTIME)
          @Target({ElementType.PARAMETER})
          public?@interface?RsaVerify?{
          ????
          ????/**
          ?????*?是否啟用驗簽功能,默認驗簽
          ?????*/

          ????boolean?verifySign()?default?true;
          }

          自定義方法參數(shù)解析器

          @AllArgsConstructor
          @Component
          //實現(xiàn)?HandlerMethodArgumentResolver?接口
          public?class?RsaVerifyArgumentResolver?implements?HandlerMethodArgumentResolver?{

          ????private?final?SecurityService?securityService;

          ????/**
          ?????*?此方法用來判斷本次請求的接口是否需要解析參數(shù),
          ?????*?如果需要返回?true,然后調用下面的?resolveArgument?方法,
          ?????*??如果不需要返回?false
          ?????*/

          ????@Override
          ????public?boolean?supportsParameter(MethodParameter?parameter)?{
          ????????return?parameter.hasParameterAnnotation(RsaVerify.class);
          ????}

          ????/**
          ?????*?真正的解析方法,將請求中的參數(shù)值解析為某種對象
          ?????*?parameter?要解析的方法參數(shù)
          ?????*?mavContainer?當前請求的?ModelAndViewContainer(為請求提供對模型的訪問)
          ?????*?webRequest?當前請求
          ?????*?WebDataBinderFactory?用于創(chuàng)建?WebDataBinder?的工廠
          ?????*/

          ????@Override
          ????public?Object?resolveArgument(MethodParameter?parameter,?ModelAndViewContainer?mavContainer,?NativeWebRequest?webRequest,?WebDataBinderFactory?binderFactory)?throws?Exception?{
          ????????RsaVerify?parameterAnnotation?=?parameter.getParameterAnnotation(RsaVerify.class);
          ????????if?(!parameterAnnotation.verifySign())?{
          ????????????return?mavContainer.getModel();
          ????????}
          ????????
          ????????//對參數(shù)進行處理并驗簽的邏輯
          ????????......
          ????????
          ????????//返回處理后的實體類參數(shù)
          ????????return?ObjectMapperFactory
          ????????????????.getDateTimeObjectMapper("yyyyMMddHHmmss")
          ????????????????.readValue(StringUtil.queryParamsToJson(sb.toString()),?parameter.getParameterType());
          ????}
          ???
          }

          創(chuàng)建配置類

          @Configuration
          @AllArgsConstructor
          public?class?PayTenantWebConfig?implements?WebMvcConfigurer?{

          ????private?final?RsaVerifyArgumentResolver?rsaVerifyArgumentResolver;
          ????
          ????/**
          ?????*?將自定義的方法參數(shù)解析器加入到配置類中
          ?????*/

          ????@Override
          ????public?void?addArgumentResolvers(List?resolvers)?{
          ????????resolvers.add(rsaVerifyArgumentResolver);
          ????}
          }

          使用

          使用方法非常簡單,只需要在參數(shù)上引入注解就可以了

          @RestController
          @Slf4j
          @RequestMapping("/xxx")
          public?class?XxxCallbackController?{

          ????/**
          ?????*?@param?params
          ?????*?@return
          ?????*/

          ????@PostMapping("/callback")
          ????public?String?callback(@RsaVerify?CallbackReq?params)?{
          ????????log.info("receive?callback?req={}",?params);
          ??//業(yè)務邏輯處理
          ??.....
          ??
          ????????return?"success";
          ????}
          }

          問題

          問題一

          看到這,細心的朋友應該會有所疑問:既然這邊用到了自定義的注解,為什么不用切面來實現(xiàn),而是使用自定義的參數(shù)解析器呢?Very Good!這也是阿Q提出的疑問,同事說是因為?jackson?的反序列化動作優(yōu)先級遠高于切面的優(yōu)先級,所以還沒進入切面就已經(jīng)報反序列化失敗的錯誤了。

          問題二

          為什么在?controller?中注解?@RequestBody?不見了?

          要回答這個問題,我們就得了解下HandlerMethodArgumentResolverComposite這個類了,以下簡稱CompositeSpringMVC?在啟動時會將所有的參數(shù)解析器放到?Composite?中,Composite?是所有參數(shù)的一個集合。當對參數(shù)進行解析時就會從該參數(shù)解析器集合中選擇一個支持對?parameter?解析的參數(shù)解析器,然后使用該解析器進行參數(shù)解析。

          又因為@RequestBody所以使用的參數(shù)解析器RequestResponseBodyMethodProcessor優(yōu)先級高于我們自定義的參數(shù)解析器,所以如果共用會被前者攔截解析,所以為了正常使用,我們需要將@RequestBody?注解去掉。

          /**
          ?*?Find?a?registered?{@link?HandlerMethodArgumentResolver}?that?supports
          ?*?the?given?method?parameter.
          ?*/

          @Nullable
          private?HandlerMethodArgumentResolver?getArgumentResolver(MethodParameter?parameter)?{
          ????HandlerMethodArgumentResolver?result?=?this.argumentResolverCache.get(parameter);
          ????if?(result?==?null)?{
          ????????for?(HandlerMethodArgumentResolver?resolver?:?this.argumentResolvers)?{
          ????????????if?(resolver.supportsParameter(parameter))?{
          ????????????????result?=?resolver;
          ????????????????this.argumentResolverCache.put(parameter,?result);
          ????????????????break;
          ????????????}
          ????????}
          ????}
          ????return?result;
          }

          C同事的解決方案

          上邊 Z 同事的方案已經(jīng)可以解決該問題了,但是該方案還有兩個不足之處:

          • 需要每一個回調都去創(chuàng)建自己的?controller?層,沒有一個對外的統(tǒng)一入口;
          • 需要在方法上添加自定義注解,侵入性比較強;

          因此經(jīng)過我們的商議,決定摒棄該方案,但是該方案的思想值得我們學習。接下來讓我們分析一下新的解決方案:

          定義業(yè)務接口類

          業(yè)務接口類包含兩個方法:具體業(yè)務處理的類型;業(yè)務的具體處理方法。

          public?interface?INotifyService?{
          ?/**
          ??*?處理類型
          ??*/

          ?public?String?handleType();
          ?/**
          ??*?處理具體業(yè)務
          ??*/

          ?Integer?handle(String?notifyBody);

          }

          異步通知統(tǒng)一入口

          @AllArgsConstructor
          @RestController
          @RequestMapping(value?=?"/notify")
          public?class?NotifyController?{
          ?private?IService?service;

          ????@PostMapping(value?=?"/receive")
          ????public?String?receive(@RequestBody?String?body)?{
          ????????//處理通知
          ????????Integer?status?=?service.handle(body);
          ????????return?"success";
          ????}
          }

          在 Iservice 中做兩個步驟:

          • 在 spring 啟動之后,收集所有的類型為?INotifyService的類并放入map中;
          • 將參數(shù)進行處理轉化,并驗簽處理;
          private?ApplicationContext?applicationContext;
          private?Map?notifyServiceMap;

          /**
          ?*?啟動加載
          ?*/

          @PostConstruct
          public?void?init(){
          ?Map?map?=?applicationContext.getBeansOfType(INotifyService.class);
          ?Collection?services?=?map.values();
          ?if(CollectionUtils.isEmpty(services)){
          ??return;
          ?}
          ?notifyServiceMap?=?services.stream().collect(Collectors.toMap(INotifyService::handleType,?x?->?x));
          }

          @Override
          public?Map?getNotifyServiceMap()?{
          ?return?notifyServiceMap;
          }

          @Override
          public?Integer?handle(String?body)?{
          ?//參數(shù)處理+驗簽邏輯
          ????......
          ????????
          ?//獲取具體的業(yè)務實現(xiàn)類
          ?INotifyService?notifyService=notifyServiceMap.get(notifyType);
          ?Integer?status=null;
          ?if(Objects.nonNull(notifyService))?{
          ??//執(zhí)行具體業(yè)務
          ??try?{
          ???status=notifyService.handle(JSON.toJSONString(requestParameter));
          ??}?catch?(Exception?e)?{
          ???e.printStackTrace();
          ??}
          ?}
          ?//后續(xù)邏輯處理
          ????......
          ????????
          ?return?status;
          }

          業(yè)務具體實現(xiàn)

          @Service
          public?class?NotifySignServiceImpl?implements?INotifyService?{

          ????@Override
          ????public?String?handleType()?{
          ????????return?"type_sign";
          ????}

          ????@Override
          ????@Transactional
          ????public?Integer?handle(String?notifyBody)?{
          ????????//具體的業(yè)務處理
          ????????......
          ????}

          小結

          • 此方案提供統(tǒng)一的異步通知入口,把公共的參數(shù)處理和驗簽邏輯與業(yè)務邏輯剝離。
          • 利用 java 動態(tài)加載類的特性,將實現(xiàn)類通過類型進行收集。
          • 利用 java 多態(tài)的特性,通過不同的實現(xiàn)類來處理不同的業(yè)務邏輯。

          看到這,相信大家已經(jīng)對這兩種實現(xiàn)方案有了一定的理解,大家可以試著在以后的項目中應用一下,體驗一把!


          END


          頂級程序員:topcoding

          做最好的程序員社區(qū):Java后端開發(fā)、Python、大數(shù)據(jù)、AI


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


          瀏覽 31
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  天天舔天天日 | 中文字幕乱伦 | 特级西西人体444WWwtini | www.天天色综合网 | 成人精品视频在线 |