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

          一文學(xué)會注解的正確使用姿勢

          共 15583字,需瀏覽 32分鐘

           ·

          2020-09-02 16:23

          在開始本文之前簡單說幾句,有讀者反應(yīng)在面試阿里時被問了網(wǎng)關(guān)相關(guān)的內(nèi)容,這位讀者剛好看到本號之前兩篇網(wǎng)關(guān)的文章,幫上了忙,再次推薦給大家,相信大家看完肯定有收獲

          高性能接入層網(wǎng)關(guān)設(shè)計實踐

          高性能 Java 應(yīng)用層網(wǎng)關(guān)設(shè)計實踐

          前言

          日志作為排查問題的重要手段,可以說是應(yīng)用集成中必不可少的一環(huán),但在日志中,又不宜暴露像電話,身份證,地址等個人敏感信息,去年 Q4 我司就開展了對 ELK 日志脫敏的全面要求。那么怎樣快速又有效地實現(xiàn)日志脫敏呢。相信讀者看完標(biāo)題已經(jīng)猜到了,沒錯,用注解!那么用注解該怎么實現(xiàn)日志脫敏呢,除了日志脫敏,注解還能用在哪些場景呢,注解的實現(xiàn)原理又是怎樣的呢。本文將會為你詳細介紹。

          本文將會從以下幾個方面來介紹注解。

          • 日志脫敏場景簡介
          • 巧用注解解決這兩類問題
            1. 注解的定義與實現(xiàn)原理
            2. 使用注解解決日志脫敏
          • 注解高級用法-解決銀行中參數(shù)傳遞順序要求

          相信大家看了肯定有收獲!

          日志脫敏場景簡介

          在日志里我們的日志一般打印的是 model 的 Json string,比如有以下 model 類

          public?class?Request?{
          ????/**
          ?????*??用戶姓名
          ?????*/

          ????private?String?name;
          ????/**
          ?????*??身份證?
          ?????*/

          ????private?String?idcard;
          ????/**
          ?????*??手機號
          ?????*/

          ????private?String?phone;

          ????/**
          ?????*??圖片的?base64
          ?????*/

          ????private?String?imgBase64;
          }

          有以下類實例

          Request?request?=?new?Request();
          request.setName("愛新覺羅");
          request.setIdcard("450111112222");
          request.setPhone("18611111767");
          request.setImgBase64("xxx");

          我們一般使用 fastJson 來打印此 Request 的 json string:

          log.info(JSON.toJSONString(request));

          這樣就能把 Request 的所有屬性值給打印出來,日志如下:

          {"idcard":"450111112222","imgBase64":"xxx","name":"張三","phone":"17120227942"}

          這里的日志有兩個問題

          1. 安全性: name,phone, idcard 這些個人信息極其敏感,不應(yīng)以明文的形式打印出來,我們希望這些敏感信息是以脫敏的形式輸出的
          2. 字段冗余:imgBase64 是圖片的 base64,是一串非常長的字符串,在生產(chǎn)上,圖片 base64 數(shù)據(jù)對排查問題幫助不大,反而會增大存儲成本,而且這個字段是身份證正反面的 base64,也屬于敏感信息,所以這個字段在日志中需要把它去掉。我們希望經(jīng)過脫敏和瘦身(移除 imgBase64 字段)后的日志如下:
          {"idcard":"450******222","name":"愛**羅","phone":"186****1767","imgBase64":""}

          可以看到各個字段最后都脫敏了,不過需要注意的這幾個字段的脫敏規(guī)則是不一樣的

          • 身份證(idcard),保留前三位,后三位,其余打碼
          • 姓名(name)保留前后兩位,其余打碼
          • 電話號碼(phone)保持前三位,后四位,其余打碼
          • 圖片的 base64(imgBase64)直接展示空字符串

          該怎么實現(xiàn)呢,首先我們需要知道一個知識點,即 JSON.toJSONString 方法指定了一個參數(shù) ValueFilter,可以定制要轉(zhuǎn)化的屬性。我們可以利用此 Filter 讓最終的 JSON string 不展示或展示脫敏后的 value。大概邏輯如下

          public?class?Util?{
          ????public?static?String?toJSONString(Object?object)?{
          ????????try?{
          ????????????return?JSON.toJSONString(object,?getValueFilter());
          ????????}?catch?(Exception?e)?{
          ????????????return?ToStringBuilder.reflectionToString(object);
          ????????}
          ????}
          ????
          private?static?ValueFilter?getValueFilter()?{
          ????????return?(obj,?key,?value)?->?{
          ????????????//?obj-對象?key-字段名?value-字段值
          ????????????return??格式化后的value
          ????????};
          }

          如上圖示,我們只要在 getValueFilter 方法中對 value 作相關(guān)的脫敏操作,即可在最終的日志中展示脫敏后的日志。現(xiàn)在問題來了,該怎么處理字段的脫敏問題,我們知道有些字段需要脫敏,有些字段不需要脫敏,所以有人可能會根據(jù) key 的名稱來判斷是否脫敏,代碼如下:

          private?static?ValueFilter?getValueFilter()?{
          ????????return?(obj,?key,?value)?->?{
          ????????????//?obj-對象?key-字段名?value-字段值
          ????????????if?(Objects.equal(key,?"phone"))?{
          ????????????????return?脫敏后的phone
          ????????????}
          ????????????if?(Objects.equal(key,?"idcard"))?{
          ????????????????return?脫敏后的idcard
          ????????????}
          ????????????if?(Objects.equal(key,?"name"))?{
          ????????????????return?脫敏后的name
          ????????????}
          ????????????//?其余不需要脫敏的按原值返回
          ????????????return??value
          ????????};
          }

          這樣看起來確實實現(xiàn)了需求,但僅僅實現(xiàn)了需求就夠了嗎,這樣的實現(xiàn)有個比較嚴重的問題:

          脫敏規(guī)則與具體的屬性名緊藕合,需要在 valueFilter 里寫大量的 if else 判斷邏輯,可擴展性不高,通用性不強,舉個簡單的例子,由于業(yè)務(wù)原因,在我們的工程中電話有些字段名叫 phone, 有些叫 tel,有些叫 telephone,它們的脫敏規(guī)則是一樣的,但你不得不在上面的方法中寫出如下丑陋的代碼。

          private?static?ValueFilter?getValueFilter()?{
          ????????return?(obj,?key,?value)?->?{
          ????????????//?obj-對象?key-字段名?value-字段值
          ????????????if?(Objects.equal(key,?"phone")?||?Objects.equal(key,?"tel")?||?Objects.equal(key,?"telephone")?||?)?{
          ????????????????return?脫敏后的phone
          ????????????}

          ????????????//?其余不需要脫敏的按原值返回
          ????????????return??value
          ????????};
          }

          那么能否用一種通用的,可擴展性好的方法來解決呢,相信你看到文章的標(biāo)題已經(jīng)心中有數(shù)了,沒錯,就是用的注解,接下來我們來看看什么是注解以及如何自定義注解

          注解的定義與實現(xiàn)原理

          注解(Annotation)又稱 ?Java 標(biāo)注,是 JDK 5.0 引入的一種注釋機制,如果說代碼的注釋是給程序員看的,那么注解就是給程序看的,程序看到注解后就可以在運行時拿到注解,根據(jù)注解來增強運行時的能力,常見的應(yīng)用在代碼中的注解有如下三個

          • @Override 檢查該方法是否重寫了父類方法,如果發(fā)現(xiàn)父類或?qū)崿F(xiàn)的接口中沒有此方法,則報編譯錯誤
          • @Deprecated 標(biāo)記過時的類,方法,屬性等
          • @SuppressWarnings - 指示編譯器去忽略注解中聲明的警告。

          那這些注解是怎么實現(xiàn)的呢,我們打開 @Override 這個注解看看

          @Documented
          @Retention(RetentionPolicy.RUNTIME)
          @Target(value={CONSTRUCTOR,?FIELD,?LOCAL_VARIABLE,?METHOD,?PACKAGE,?PARAMETER,?TYPE})
          public?@interface?Deprecated?{
          }

          可以看到 Deprecated 注解上又有 @Documented, @Retention, @Target 這些注解,這些注解又叫元注解,即注解 Deprecated 或其他自定義注解的注解,其他注解的行為由這些注解來規(guī)范和定義,這些元注解的類型及作用如下

          • @Documented 表明它會被 javadoc 之類的工具處理, 這樣最終注解類型信息也會被包括在生成的文檔中

          • @Retention ?注解的保存策略,主要有以下三種

            • RetentionPolicy.SOURCE 源代碼級別的注解,表示指定的注解只在編譯期可見,并不會寫入字節(jié)碼文件,Override, SuppressWarnings 就屬于此類型,這類注解對于程序員來說主要起到在編譯時提醒的作用,在運行保存意義并不大,所以最終并不會被編譯入字節(jié)碼文件中
            • RetentionPolicy.RUNTIME ?表示注解會被編譯入最終的字符碼文件中,JVM 啟動后也會讀入注解,這樣我們在運行時就可以通過反射來獲取這些注解,根據(jù)這些注解來做相關(guān)的操作,這是多數(shù)自定義注解使用的保存策略,這里可能大家有個疑問,為啥 Deprecated 被標(biāo)為 RUNTIME 呢,對于程序員來說,理論上來說只關(guān)心調(diào)用的類,方法等是否 Deprecated 就夠了,運行時獲取有啥意義呢,考慮這樣一種場景,假設(shè)你想在生產(chǎn)上統(tǒng)計過時的方法被調(diào)用的頻率以評估你工程的壞味道或作為重構(gòu)參考,此時這個注解是不是派上用場了。
            • RetentionPolicy.CLASS 注解會被編譯入最終的字符碼文件,但并不會載入 JVM 中(在類加載的時候注解會被丟棄),這種保存策略不常用,主要用在字節(jié)碼文件的處理中。
          • @Target 表示該注解可以用在什么地方,默認情況下可以用在任何地方,該注解的作用域主要通過 value 來指定,這里列舉幾個比較常見的類型:

            • FIELD 作用于屬性
            • METHOD 作用于方法
            • ElementType.TYPE: 作用于類、接口(包括注解類型) 或 enum 聲明
          • @Inherited - 標(biāo)記這個注解是繼承于哪個注解類(默認 注解并沒有繼承于任何子類)

          再來看 @interface, 這個是干啥用的,其實如果你反編譯之后就會發(fā)現(xiàn)在字節(jié)碼中編譯器將其編碼成了如下內(nèi)容。

          public?interface?Override?extends?Annotation?{???
          }

          Annotation 是啥


          我們可以看出注解的本質(zhì)其實是繼承了 Annotation 這個接口的接口,并且輔以 Retention,Target 這些規(guī)范注解運行時行為,作用域等的元注解。

          Deprecated 注解中沒有定義屬性,其實如果需要注解是可以定義屬性的,比如 Deprecated 注解可以定義一個 value 的屬性,在聲明注解的時候可以指定此注解的 value 值

          @Documented
          @Retention(RetentionPolicy.RUNTIME)
          @Target(value={CONSTRUCTOR,?FIELD,?LOCAL_VARIABLE,?METHOD,?PACKAGE,?PARAMETER,?TYPE})
          public?@interface?Deprecated?{
          ????String?value()?default?"";
          }

          這樣我將此注解應(yīng)用于屬性等地方時,可以指定此 value ?值,如下所示

          public?class?Person?{
          ????@Deprecated(value?=?"xxx")
          ????private?String?tail;
          }

          如果注解的保存策略為 RetentionPolicy.RUNTIME,我們就可以用如下方式在運行時獲取注解,進而獲取注解的屬性值等

          field.getAnnotation(Deprecated.class);

          巧用注解解決日志脫敏問題

          上文簡述了注解的原理與寫法,接下來我們來看看如何用注解來實現(xiàn)我們的日志脫敏。

          首先我們要定義一下脫敏的注解,由于此注解需要在運行時被取到,所以保存策略要為 RetentionPolicy.RUNTIME,另外此注解要應(yīng)用于 phone,idcard 這些字段,所以@Target 的值為 ElementType.FIELD,另外我們注意到,像電話號碼,身份證這些字段雖然都要脫敏,但是它們的脫敏策略不一樣,所以我們需要為此注解定義一個屬性,這樣可以指定它的屬性屬于哪種脫敏類型,我們定義的脫敏注解如下:

          //?敏感信息類型
          public?enum?SensitiveType?{
          ????ID_CARD,?PHONE,?NAME,?IMG_BASE64
          }

          @Target({?ElementType.FIELD?})
          @Retention(RetentionPolicy.RUNTIME)
          public?@interface?SensitiveInfo?{
          ????SensitiveType?type();
          }

          定義好了注解,現(xiàn)在就可以為我們的敏感字段指定注解及其敏感信息類型了,如下

          public?class?Request?{
          ????@SensitiveInfo(type?=?SensitiveType.NAME)
          ????private?String?name;
          ????@SensitiveInfo(type?=?SensitiveType.ID_CARD)
          ????private?String?idcard;
          ????@SensitiveInfo(type?=?SensitiveType.PHONE)
          ????private?String?phone;
          ????@SensitiveInfo(type?=?SensitiveType.IMG_BASE64)
          ????private?String?imgBase64;
          }

          為屬性指定好了注解,該怎么根據(jù)注解來實現(xiàn)相應(yīng)敏感字段類型的脫敏呢,可以用反射,先用反射獲取類的每一個 Field,再判定 Field 上是否有相應(yīng)的注解,若有,再判斷此注解是針對哪種敏感類型的注解,再針對相應(yīng)字段做相應(yīng)的脫敏操作,直接上代碼,注釋寫得很清楚了,相信大家應(yīng)該能看懂

          private?static?ValueFilter?getValueFilter()?{
          ????????return?(obj,?key,?value)?->?{
          ????????????//?obj-對象?key-字段名?value-字段值
          ????????????try?{
          ????????????????//?通過反射獲取獲取每個類的屬性
          ????????????????Field[]?fields?=?obj.getClass().getDeclaredFields();
          ????????????????for?(Field?field?:?fields)?{
          ????????????????????if?(!field.getName().equals(key))?{
          ????????????????????????continue;
          ????????????????????}
          ????????????????????//?判定屬性是否有相應(yīng)的?SensitiveInfo?注解
          ????????????????????SensitiveInfo?annotation?=?field.getAnnotation(SensitiveInfo.class);
          ????????????????????//?若有,則執(zhí)行相應(yīng)字段的脫敏方法
          ????????????????????if?(null?!=?annotation)?{
          ????????????????????????switch?(annotation.type())?{
          ????????????????????????????case?PHONE:
          ????????????????????????????????return?電話脫敏;
          ????????????????????????????case?ID_CARD:
          ????????????????????????????????return?身份證脫敏;
          ????????????????????????????case?NAME:
          ????????????????????????????????return?姓名脫敏;
          ????????????????????????????case?IMG_BASE64:
          ????????????????????????????????return?"";?//?圖片的?base?64?不展示,直接返回空
          ????????????????????????????default:
          ????????????????????????????????//?這里可以拋異常
          ????????????????????????}
          ????????????????????}
          ????????????????????}
          ????????????????}
          ????????????}?catch?(Exception?e)?{
          ????????????????log.error("To?JSON?String?fail",?e);
          ????????????}
          ????????????return?value;
          ????????};
          ????}

          有人可能會說了,使用注解的方式來實現(xiàn)脫敏代碼量翻了一倍不止,看起來好像不是很值得,其實不然,之前的方式,脫敏規(guī)則與某個字段名強藕合,可維護性不好,而用注解的方式,就像工程中出現(xiàn)的 phone, tel,telephone 這些都屬于電話脫敏類型的,只要統(tǒng)一標(biāo)上 ?**@SensitiveInfo(type = SensitiveType.PHONE) ** 這樣的注解即可,而且后續(xù)如有新的脫敏類型,只要重新加一個 SensitiveType 的類型即可,可維護性與擴展性大大增強。所以在這類場景中,使用注解是強烈推薦的。

          注解的高級應(yīng)用-利用注解消除重復(fù)代碼

          在與銀行對接的過程中,銀行提供了一些 API 接口,對參數(shù)的序列化有點特殊,不使用 JSON,而是需要我們把參數(shù)依次拼在一起構(gòu)成一個大字符串。

          • 按照銀行提供的 API 文檔的順序,把所有參數(shù)構(gòu)成定長的數(shù)據(jù),然后拼接在一起作為整個字符串。
          • 因為每一種參數(shù)都有固定長度,未達到長度時需要做填充處理:
            • 字符串類型的參數(shù)不滿長度部分需要以下劃線右填充,也就是字符串內(nèi)容靠左;
            • 數(shù)字類型的參數(shù)不滿長度部分以 0 左填充,也就是實際數(shù)字靠右;
            • 貨幣類型的表示需要把金額向下舍入 2 位到分,以分為單位,作為數(shù)字類型同樣進行左填充。
          • 對所有參數(shù)做 MD5 操作作為簽名(為了方便理解,Demo 中不涉及加鹽處理)。簡單看兩個銀行的接口定義

          1、創(chuàng)建用戶

          在這里插入圖片描述

          2、支付接口

          常規(guī)的做法是為每個接口都根據(jù)之前的規(guī)則填充參數(shù),拼接,驗簽,以以上兩個接口為例,先看看常規(guī)做法

          創(chuàng)建用戶與支付的請求如下:

          //?創(chuàng)建用戶?POJO
          @Data
          public?class?CreateUserRequest?{?
          ????private?String?name;?
          ????private?String?identity;?
          ????private?String?mobile;
          ????private?int?age;
          }

          //?支付?POJO
          @Data
          public?class?PayRequest?{?
          ????private?long?userId;?
          ????private?BigDecimal?amount;
          }

          public?class?BankService?{

          ????//創(chuàng)建用戶方法
          ????public?static?String?createUser(CreateUserRequest?request)?throws?IOException?{
          ????????StringBuilder?stringBuilder?=?new?StringBuilder();
          ????????//字符串靠左,多余的地方填充_
          ????????stringBuilder.append(String.format("%-10s",?request.getName()).replace('?',?'_'));
          ????????//字符串靠左,多余的地方填充_
          ????????stringBuilder.append(String.format("%-18s",?request.getIdentity()).replace('?',?'_'));
          ????????//數(shù)字靠右,多余的地方用0填充
          ????????stringBuilder.append(String.format("%05d",?age));
          ????????//字符串靠左,多余的地方用_填充
          ????????stringBuilder.append(String.format("%-11s",?mobile).replace('?',?'_'));
          ????????//最后加上MD5作為簽名
          ????????stringBuilder.append(DigestUtils.md2Hex(stringBuilder.toString()));
          ????????return?Request.Post("http://baseurl/createUser")
          ????????????????.bodyString(stringBuilder.toString(),?ContentType.APPLICATION_JSON)
          ????????????????.execute().returnContent().asString();
          ????}
          ????
          ????//支付方法
          ????public?static?String?pay(PayRequest?request)?throws?IOException?{
          ????????StringBuilder?stringBuilder?=?new?StringBuilder();
          ????????//數(shù)字靠右,多余的地方用0填充
          ????????stringBuilder.append(String.format("%020d",?request.getUserId()));
          ????????//金額向下舍入2位到分,以分為單位,作為數(shù)字靠右,多余的地方用0填充
          ?stringBuilder.append(String.format("%010d",request.getAmount().setScale(2,RoundingMode.DOWN).multiply(new??????????????????????????????????????????????????????????????????????????????????BigDecimal("100")).longValue()));?
          ????????//最后加上MD5作為簽名
          ????????stringBuilder.append(DigestUtils.md2Hex(stringBuilder.toString()));
          ????????return?Request.Post("http://baseurl//pay")
          ????????????????.bodyString(stringBuilder.toString(),?ContentType.APPLICATION_JSON)
          ????????????????.execute().returnContent().asString();
          ????}
          }

          可以看到光寫這兩個請求,邏輯就有很多重復(fù)的地方:

          1、 字符串,貨幣,數(shù)字三種類型的格式化邏輯大量重復(fù),以處理字符串為例


          可以看到,格式化字符串的的處理只是每個字段的長度不同,其余格式化規(guī)則完全一樣,但在上文中我們卻為每一個字符串都整了一套相同的處理邏輯,這套拼接規(guī)則完全可以抽出來(因為只是長度不一樣,拼接規(guī)則是一樣的)

          2、 處理流程中字符串拼接、加簽和發(fā)請求的邏輯,在所有方法重復(fù)。

          3、 由于每個字段參與拼接的順序不一樣,這些需要我們?nèi)巳庥簿幋a保證這些字段的順序,維護成本極大,而且很容易出錯,想象一下如果參數(shù)達到幾十上百個,這些參數(shù)都需要按一定順序來拼接,如果要人肉來保證,很難保證正確性,而且重復(fù)工作太多,得不償失

          接下來我們來看看如何用注解來極大簡化我們的代碼。

          1、 首先對于每一個調(diào)用接口來說,它們底層都是需要請求網(wǎng)絡(luò)的,只是請求方法不一樣,針對這一點 ,我們可以搞一個如下針對接口的注解

          @Retention(RetentionPolicy.RUNTIME)
          @Target(ElementType.TYPE)
          @Documented
          @Inherited
          public?@interface?BankAPI?{?
          ????String?url()?default?"";
          ????String?desc()?default?"";?
          }

          這樣在網(wǎng)絡(luò)請求層即可統(tǒng)一通過注解獲取相應(yīng)接口的方法名

          2、 針對每個請求接口的 POJO,我們注意到每個屬性都有 類型(字符串/數(shù)字/貨幣)長度順序這三個屬性,所以可以定義一個注解,包含這三個屬性,如下

          @Retention(RetentionPolicy.RUNTIME)
          @Target(ElementType.FIELD)
          @Documented
          @Inherited
          public?@interface?BankAPIField?{
          ????int?order()?default?-1;
          ????int?length()?default?-1;
          ????String?type()?default?"";?//?M代表貨幣,S代表字符串,N代表數(shù)字
          }

          接下來我們將上文中定義的注解應(yīng)用到上文中的請求 POJO 中

          對于創(chuàng)建用戶請求

          @BankAPI(url?=?"/createUser",?desc?=?"創(chuàng)建用戶接口")
          @Data
          public?class?CreateUserAPI?extends?AbstractAPI?{
          ????@BankAPIField(order?=?1,?type?=?"S",?length?=?10)
          ????private?String?name;
          ????@BankAPIField(order?=?2,?type?=?"S",?length?=?18)
          ????private?String?identity;
          ????@BankAPIField(order?=?4,?type?=?"S",?length?=?11)?//注意這里的order需要按照API表格中的順序
          ????private?String?mobile;
          ????@BankAPIField(order?=?3,?type?=?"N",?length?=?5)
          ????private?int?age;
          }

          對于支付接口

          @BankAPI(url?=?"/bank/pay",?desc?=?"支付接口")
          @Data
          public?class?PayAPI?extends?AbstractAPI?{
          ????@BankAPIField(order?=?1,?type?=?"N",?length?=?20)
          ????private?long?userId;
          ????@BankAPIField(order?=?2,?type?=?"M",?length?=?10)
          ????private?BigDecimal?amount;
          }

          接下來利用注解來調(diào)用的流程如下

          1. 根據(jù)反射獲取類的 Field 數(shù)組,然后再根據(jù) Field 的 BankAPIField 注解中的 order 值對 Field 進行排序
          2. 對排序后的 Field 依次進行遍歷,首先判斷其類型,然后根據(jù)類型再對其值格式化,如判斷為"S",則按接口要求字符串的格式對其值進行格式化,將這些格式化后的 Field 值依次拼接起來并進行簽名
          3. 拼接后就是發(fā)請求了,此時再拿到 POJO 類的注解,獲取注解 BankAPI ?的 url 值,將其與 baseUrl 組合起來即可構(gòu)成一個完整的的 url,再加上第 2 步中拼接字符串即可構(gòu)造一個完全的請求

          代碼如下:


          private?static?String?remoteCall(AbstractAPI?api)?throws?IOException?{
          ????//從BankAPI注解獲取請求地址
          ????BankAPI?bankAPI?=?api.getClass().getAnnotation(BankAPI.class);
          ????bankAPI.url();
          ????StringBuilder?stringBuilder?=?new?StringBuilder();
          ????Arrays.stream(api.getClass().getDeclaredFields())?//獲得所有字段
          ????????????.filter(field?->?field.isAnnotationPresent(BankAPIField.class))?//查找標(biāo)記了注解的字段
          ????????????.sorted(Comparator.comparingInt(a?->?a.getAnnotation(BankAPIField.class).order()))?//根據(jù)注解中的order對字段排序
          ????????????.peek(field?->?field.setAccessible(true))?//設(shè)置可以訪問私有字段
          ????????????.forEach(field?->?{
          ????????????????//獲得注解
          ????????????????BankAPIField?bankAPIField?=?field.getAnnotation(BankAPIField.class);
          ????????????????Object?value?=?"";
          ????????????????try?{
          ????????????????????//反射獲取字段值
          ????????????????????value?=?field.get(api);
          ????????????????}?catch?(IllegalAccessException?e)?{
          ????????????????????e.printStackTrace();
          ????????????????}
          ????????????????//根據(jù)字段類型以正確的填充方式格式化字符串
          ????????????????switch?(bankAPIField.type())?{
          ????????????????????case?"S":?{
          ????????????????????????stringBuilder.append(String.format("%-"?+?bankAPIField.length()?+?"s",?value.toString()).replace('?',?'_'));
          ????????????????????????break;
          ????????????????????}
          ????????????????????case?"N":?{
          ????????????????????????stringBuilder.append(String.format("%"?+?bankAPIField.length()?+?"s",?value.toString()).replace('?',?'0'));
          ????????????????????????break;
          ????????????????????}
          ????????????????????case?"M":?{
          ????????????????????????if?(!(value?instanceof?BigDecimal))
          ????????????????????????????throw?new?RuntimeException(String.format("{}?的?{}?必須是BigDecimal",?api,?field));
          ????????????????????????stringBuilder.append(String.format("%0"?+?bankAPIField.length()?+?"d",?((BigDecimal)?value).setScale(2,?RoundingMode.DOWN).multiply(new?BigDecimal("100")).longValue()));
          ????????????????????????break;
          ????????????????????}
          ????????????????????default:
          ????????????????????????break;
          ????????????????}
          ????????????});
          ????//簽名邏輯
          ??? stringBuilder.append(DigestUtils.md2Hex(stringBuilder.toString()));
          ????String?param?=?stringBuilder.toString();
          ????long?begin?=?System.currentTimeMillis();
          ????//發(fā)請求
          ????String?result?=?Request.Post("http://localhost:45678/reflection"?+?bankAPI.url())
          ????????????.bodyString(param,?ContentType.APPLICATION_JSON)
          ????????????.execute().returnContent().asString();
          ????log.info("調(diào)用銀行API?{}?url:{}?參數(shù):{}?耗時:{}ms",?bankAPI.desc(),?bankAPI.url(),?param,?System.currentTimeMillis()?-?begin);
          ????return?result;
          }

          現(xiàn)在再來看一下創(chuàng)建用戶和付款的邏輯


          //創(chuàng)建用戶方法
          public?static?String?createUser(CreateUserAPI?request)?throws?IOException?{
          ????return?remoteCall(request);
          }
          //支付方法
          public?static?String?pay(PayAPI?request)?throws?IOException?{
          ????return?remoteCall(request);
          }

          可以看到所有的請求現(xiàn)在都只要統(tǒng)一調(diào)用 ?remoteCall 這個方法即可,remoteCall 這個方法統(tǒng)一了所有請求的邏輯,省略了巨量無關(guān)的代碼,讓代碼的可維護性大大增強!使用注解和反射讓我們可以對這類結(jié)構(gòu)性的問題進行通用化處理,確實 Cool!

          總結(jié)

          如果說反射給了我們在不知曉類結(jié)構(gòu)的情況下按照固定邏輯處理類成員的能力的話,注解則是擴展補充了這些成員的元數(shù)據(jù)的能力,使用得我們在利用反射實現(xiàn)通用邏輯的時候,可以從外部獲取更多我們關(guān)心的數(shù)據(jù),進而對這些數(shù)據(jù)進行通用的處理,巧用反射,確實能讓我們達到事半功倍的效果,能極大的減少重復(fù)代碼,有效解藕,使擴展性大大提升。

          額外提一句,上文中注解的高級應(yīng)用的例子來自文末的參考鏈接中的一個例子,如果大家沒有訂閱教程,可以加我好友(geekoftaste),邀請你試讀此文

          巨人的肩膀

          https://time.geekbang.org/column/article/228964

          1.?人人都能看懂的 6 種限流實現(xiàn)方案!

          2.?一個空格引發(fā)的“慘案“

          3.?大型網(wǎng)站架構(gòu)演化發(fā)展歷程

          4.?Java語言“坑爹”排行榜TOP 10

          5. 我是一個Java類(附帶精彩吐槽)

          6. 看完這篇Redis緩存三大問題,保你能和面試官互扯

          7. 程序員必知的 89 個操作系統(tǒng)核心概念

          8. 深入理解 MySQL:快速學(xué)會分析SQL執(zhí)行效率

          9. API 接口設(shè)計規(guī)范

          10. Spring Boot 面試,一個問題就干趴下了!



          掃碼二維碼關(guān)注我


          ·end·

          —如果本文有幫助,請分享到朋友圈吧—

          我們一起愉快的玩耍!



          你點的每個贊,我都認真當(dāng)成了喜歡

          瀏覽 39
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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免费人日本网站. | 国产女人18毛片水18精品变态 | 俺去大香蕉 | 曰本大香蕉视频 | 最新国产中文字幕在线播放 |