1. 看了同事寫(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í)體類參數(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";
        ????}
        }

        問(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è)類了,以下簡(jiǎn)稱Composite。SpringMVC 在啟動(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ù)接口類

        業(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)類通過(guò)類型進(jìn)行收集。
        • 利用 java 多態(tài)的特性,通過(guò)不同的實(shí)現(xiàn)類來(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)
          
          

            1. 国产精品国产三级国产专区51 | 中国乱伦无码 | 午夜伦情电午夜伦情电影如如视频 | 男生插美女| 操美女逼逼 |