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

          看了同事寫(xiě)的代碼,我竟然開(kāi)始默默的模仿了。。。

          共 5874字,需瀏覽 12分鐘

           ·

          2021-11-20 23:59

          背景

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

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

          Z同事的解決方案

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

          自定義注解

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

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

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

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

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

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

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

          ????/**
          ?????*?真正的解析方法,將請(qǐng)求中的參數(shù)值解析為某種對(duì)象
          ?????*?parameter?要解析的方法參數(shù)
          ?????*?mavContainer?當(dāng)前請(qǐng)求的?ModelAndViewContainer(為請(qǐng)求提供對(duì)模型的訪問(wèn))
          ?????*?webRequest?當(dāng)前請(qǐng)求
          ?????*?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();
          ????????}
          ????????
          ????????//對(duì)參數(shù)進(jìn)行處理并驗(yàn)簽的邏輯
          ????????......
          ????????
          ????????//返回處理后的實(shí)體類(lèi)參數(shù)
          ????????return?ObjectMapperFactory
          ????????????????.getDateTimeObjectMapper("yyyyMMddHHmmss")
          ????????????????.readValue(StringUtil.queryParamsToJson(sb.toString()),?parameter.getParameterType());
          ????}
          ???
          }

          創(chuàng)建配置類(lèi)

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

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

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

          使用

          使用方法非常簡(jiǎn)單,只需要在參數(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è)務(wù)邏輯處理
          ??.....
          ??
          ????????return?"success";
          ????}
          }

          問(wèn)題

          問(wèn)題一

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

          問(wèn)題二

          為什么在 controller 中注解 @RequestBody 不見(jiàn)了?

          要回答這個(gè)問(wèn)題,我們就得了解下HandlerMethodArgumentResolverComposite這個(gè)類(lèi)了,以下簡(jiǎn)稱(chēng)CompositeSpringMVC 在啟動(dòng)時(shí)會(huì)將所有的參數(shù)解析器放到 Composite 中,Composite 是所有參數(shù)的一個(gè)集合。當(dāng)對(duì)參數(shù)進(jìn)行解析時(shí)就會(huì)從該參數(shù)解析器集合中選擇一個(gè)支持對(duì) parameter 解析的參數(shù)解析器,然后使用該解析器進(jìn)行參數(shù)解析。

          又因?yàn)?code style="font-size: 14px;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">@RequestBody所以使用的參數(shù)解析器RequestResponseBodyMethodProcessor優(yōu)先級(jí)高于我們自定義的參數(shù)解析器,所以如果共用會(huì)被前者攔截解析,所以為了正常使用,我們需要將@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)可以解決該問(wèn)題了,但是該方案還有兩個(gè)不足之處:

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

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

          定義業(yè)務(wù)接口類(lèi)

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

          public?interface?INotifyService?{
          ?/**
          ??*?處理類(lèi)型
          ??*/

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

          ?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 中做兩個(gè)步驟:

          • 在 spring 啟動(dòng)之后,收集所有的類(lèi)型為 INotifyService的類(lèi)并放入map中;
          • 將參數(shù)進(jìn)行處理轉(zhuǎn)化,并驗(yàn)簽處理;
          private?ApplicationContext?applicationContext;
          private?Map?notifyServiceMap;

          /**
          ?*?啟動(dòng)加載
          ?*/

          @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àn)簽邏輯
          ????......
          ????????
          ?//獲取具體的業(yè)務(wù)實(shí)現(xiàn)類(lèi)
          ?INotifyService?notifyService=notifyServiceMap.get(notifyType);
          ?Integer?status=null;
          ?if(Objects.nonNull(notifyService))?{
          ??//執(zhí)行具體業(yè)務(wù)
          ??try?{
          ???status=notifyService.handle(JSON.toJSONString(requestParameter));
          ??}?catch?(Exception?e)?{
          ???e.printStackTrace();
          ??}
          ?}
          ?//后續(xù)邏輯處理
          ????......
          ????????
          ?return?status;
          }

          業(yè)務(wù)具體實(shí)現(xiàn)

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

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

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

          小結(jié)

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

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

          福利篇

          最近我在掘金獲得了兩個(gè)小冊(cè)5折優(yōu)惠碼,不知道大家有沒(méi)有興趣。在本文下留言“說(shuō)出你對(duì)閱讀同事代碼的感受”即可參與活動(dòng),阿Q會(huì)從留言中選出兩位讀者免費(fèi)贈(zèng)送。

          以上就是今天的全部?jī)?nèi)容了,如果你有不同的意見(jiàn)或者更好的idea,歡迎聯(lián)系阿Q,添加阿Q可以加入技術(shù)交流群參與討論呦!

          文章風(fēng)格多變,配圖通俗易懂,故事生動(dòng)有趣。推薦幾篇文章,何不試著讀讀呢?


          覺(jué)得還不錯(cuò)?記得一鍵四連呦??

          瀏覽 69
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  日本aa一级 | 日日91精品 | 一区二区久久在线 | 欧美成人午夜无码A片秀色直播 | 久操资源|