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

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

          共 6072字,需瀏覽 13分鐘

           ·

          2021-11-26 22:03

          背景

          事情是這樣的,目前我正在參與 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ù)解析器”的解決方案,接下來我們通過代碼來了解一下。

          自定義注解

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

          ????/**
          ?????*?此方法用來判斷本次請(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ì)模型的訪問)
          ?????*?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í)體類參數(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);
          ????}
          }

          使用

          使用方法非常簡(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";
          ????}
          }

          問題

          問題一

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

          問題二

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

          要回答這個(gè)問題,我們就得了解下HandlerMethodArgumentResolverComposite這個(gè)類了,以下簡(jiǎn)稱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)可以解決該問題了,但是該方案還有兩個(gè)不足之處:

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

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

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

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

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

          ?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)之后,收集所有的類型為 INotifyService的類并放入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)類
          ?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)加載類的特性,將實(shí)現(xiàn)類通過類型進(jìn)行收集。
          • 利用 java 多態(tài)的特性,通過不同的實(shí)現(xiàn)類來處理不同的業(yè)務(wù)邏輯。

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

          ············? END? ··············

          也許你還想看
          ??|?官宣!我升級(jí)了!!!
          ? |?我在 B 站淘了 2 個(gè) Java 實(shí)戰(zhàn)項(xiàng)目! 小破站,YYDS!
          ? |?1w+字的 Dubbo 面試題/知識(shí)點(diǎn)總結(jié)!(2021 最新版)
          ? |?7年前,24歲,出版了一本 Redis 神書
          ? |?京東二面:為什么需要分布式ID?你項(xiàng)目中是怎么做的?
          ? |?再見 Spring Task,這個(gè)定時(shí)任務(wù)框架真香!
          ? |?一鍵生成數(shù)據(jù)庫文檔,堪稱數(shù)據(jù)庫界的Swagger
          ? |?來看看這個(gè)超好用的項(xiàng)目腳手架吧!5分鐘搭建一個(gè)Spring Boot 前后端分離系統(tǒng)!
          ? |?阿里開源的15個(gè)頂級(jí)Java項(xiàng)目!!!

          我是 Guide哥,一個(gè)工作2年有余,接觸編程已經(jīng)6年有余的程序員。大三開源 JavaGuide,目前已經(jīng) 100k+ Star。未來幾年,希望持續(xù)完善 JavaGuide,爭(zhēng)取能夠幫助更多學(xué)習(xí) Java 的小伙伴!共勉!凎!點(diǎn)擊即可了解我的個(gè)人經(jīng)歷

          簡(jiǎn)歷指導(dǎo)/Java 學(xué)習(xí)/面試指導(dǎo)/面試小冊(cè),歡迎加入我的知識(shí)星球(公眾號(hào)后臺(tái)回復(fù)“星球”即可)。

          瀏覽 63
          點(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>
                  乱伦片中文字幕 | 日韩A片免费在线观看 | 噜噜噜久久亚洲精品国产品 | 黑人大吊一级干炮 | 天天干天天谢 |