日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

怎么实现注解_通透!一口气搞懂注解到底怎么用

發(fā)布時(shí)間:2024/7/23 编程问答 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 怎么实现注解_通透!一口气搞懂注解到底怎么用 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

日志脫敏場(chǎng)景簡(jiǎn)介

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

public?class?Request?{????/**?????*??用戶姓名?????*/????private?String?name;????/**?????*??身份證??????*/????private?String?idcard;????/**?????*??手機(jī)號(hào)?????*/????private?String?phone;????/**?????*??圖片的?base64?????*/????private?String?imgBase64;}

有以下類實(shí)例

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

我們一般使用 fastJson 來(lái)打印此 Request 的 json string:

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

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

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

這里的日志有兩個(gè)問(wèn)題

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

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

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

    該怎么實(shí)現(xiàn)呢,首先我們需要知道一個(gè)知識(shí)點(diǎn),即 JSON.toJSONString 方法指定了一個(gè)參數(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-對(duì)象?key-字段名?value-字段值????????????return??格式化后的value????????};}

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

    private?static?ValueFilter?getValueFilter()?{????????return?(obj,?key,?value)?->?{????????????//?obj-對(duì)象?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????????};}

    這樣看起來(lái)確實(shí)實(shí)現(xiàn)了需求,但僅僅實(shí)現(xiàn)了需求就夠了嗎,這樣的實(shí)現(xiàn)有個(gè)比較嚴(yán)重的問(wèn)題:

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

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

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

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

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

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

    那這些注解是怎么實(shí)現(xiàn)的呢,我們打開(kāi) @Override 這個(gè)注解看看

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

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

    • @Documented 表明它會(huì)被 javadoc 之類的工具處理, 這樣最終注解類型信息也會(huì)被包括在生成的文檔中
    • @Retention 注解的保存策略,主要有以下三種
      • RetentionPolicy.SOURCE 源代碼級(jí)別的注解,表示指定的注解只在編譯期可見(jiàn),并不會(huì)寫(xiě)入字節(jié)碼文件,Override, SuppressWarnings 就屬于此類型,這類注解對(duì)于程序員來(lái)說(shuō)主要起到在編譯時(shí)提醒的作用,在運(yùn)行保存意義并不大,所以最終并不會(huì)被編譯入字節(jié)碼文件中
      • RetentionPolicy.RUNTIME 表示注解會(huì)被編譯入最終的字符碼文件中,JVM 啟動(dòng)后也會(huì)讀入注解,這樣我們?cè)谶\(yùn)行時(shí)就可以通過(guò)反射來(lái)獲取這些注解,根據(jù)這些注解來(lái)做相關(guān)的操作,這是多數(shù)自定義注解使用的保存策略,這里可能大家有個(gè)疑問(wèn),為啥 Deprecated 被標(biāo)為 RUNTIME 呢,對(duì)于程序員來(lái)說(shuō),理論上來(lái)說(shuō)只關(guān)心調(diào)用的類,方法等是否 Deprecated 就夠了,運(yùn)行時(shí)獲取有啥意義呢,考慮這樣一種場(chǎng)景,假設(shè)你想在生產(chǎn)上統(tǒng)計(jì)過(guò)時(shí)的方法被調(diào)用的頻率以評(píng)估你工程的壞味道或作為重構(gòu)參考,此時(shí)這個(gè)注解是不是派上用場(chǎng)了。
      • RetentionPolicy.CLASS 注解會(huì)被編譯入最終的字符碼文件,但并不會(huì)載入 JVM 中(在類加載的時(shí)候注解會(huì)被丟棄),這種保存策略不常用,主要用在字節(jié)碼文件的處理中。
    • @Target 表示該注解可以用在什么地方,默認(rèn)情況下可以用在任何地方,該注解的作用域主要通過(guò) value 來(lái)指定,這里列舉幾個(gè)比較常見(jiàn)的類型:
      • FIELD 作用于屬性
      • METHOD 作用于方法
      • ElementType.TYPE: 作用于類、接口(包括注解類型) 或 enum 聲明
    • @Inherited - 標(biāo)記這個(gè)注解是繼承于哪個(gè)注解類(默認(rèn) 注解并沒(méi)有繼承于任何子類)

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

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

    Annotation 是啥

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

    Deprecated 注解中沒(méi)有定義屬性,其實(shí)如果需要注解是可以定義屬性的,比如 Deprecated 注解可以定義一個(gè) value 的屬性,在聲明注解的時(shí)候可以指定此注解的 value 值

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

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

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

    如果注解的保存策略為 RetentionPolicy.RUNTIME,我們就可以用如下方式在運(yùn)行時(shí)獲取注解,進(jìn)而獲取注解的屬性值等

    field.getAnnotation(Deprecated.class);

    巧用注解解決日志脫敏問(wèn)題

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

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

    //?敏感信息類型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ù)注解來(lái)實(shí)現(xiàn)相應(yīng)敏感字段類型的脫敏呢,可以用反射,先用反射獲取類的每一個(gè) Field,再判定 Field 上是否有相應(yīng)的注解,若有,再判斷此注解是針對(duì)哪種敏感類型的注解,再針對(duì)相應(yīng)字段做相應(yīng)的脫敏操作,直接上代碼,注釋寫(xiě)得很清楚了,相信大家應(yīng)該能看懂

    private?static?ValueFilter?getValueFilter()?{????????return?(obj,?key,?value)?->?{????????????//?obj-對(duì)象?key-字段名?value-字段值????????????try?{????????????????//?通過(guò)反射獲取獲取每個(gè)類的屬性????????????????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;????????};????}

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

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

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

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

    1、創(chuàng)建用戶

    2、支付接口

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

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

    //?創(chuàng)建用戶?POJO@Datapublic?class?CreateUserRequest?{?????private?String?name;?????private?String?identity;?????private?String?mobile;????private?int?age;}//?支付?POJO@Datapublic?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();????}}

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

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

    可以看到,格式化字符串的的處理只是每個(gè)字段的長(zhǎng)度不同,其余格式化規(guī)則完全一樣,但在上文中我們卻為每一個(gè)字符串都整了一套相同的處理邏輯,這套拼接規(guī)則完全可以抽出來(lái)(因?yàn)橹皇情L(zhǎng)度不一樣,拼接規(guī)則是一樣的)

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

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

    接下來(lái)我們來(lái)看看如何用注解來(lái)極大簡(jiǎn)化我們的代碼。

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

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

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

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

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

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

    對(duì)于創(chuàng)建用戶請(qǐng)求

    @BankAPI(url?=?"/createUser",?desc?=?"創(chuàng)建用戶接口")@Datapublic?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;}

    對(duì)于支付接口

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

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

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

    private?static?String?remoteCall(AbstractAPI?api)?throws?IOException?{????//從BankAPI注解獲取請(qǐng)求地址????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對(duì)字段排序????????????.peek(field?->?field.setAccessible(true))?//設(shè)置可以訪問(wèn)私有字段????????????.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ā)請(qǐng)求????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ù):{}?耗時(shí):{}ms",?bankAPI.desc(),?bankAPI.url(),?param,?System.currentTimeMillis()?-?begin);????return?result;}

    現(xiàn)在再來(lái)看一下創(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);}

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

    總結(jié)

    如果說(shuō)反射給了我們?cè)诓恢獣灶惤Y(jié)構(gòu)的情況下按照固定邏輯處理類成員的能力的話,注解則是擴(kuò)展補(bǔ)充了這些成員的元數(shù)據(jù)的能力,使用得我們?cè)诶梅瓷鋵?shí)現(xiàn)通用邏輯的時(shí)候,可以從外部獲取更多我們關(guān)心的數(shù)據(jù),進(jìn)而對(duì)這些數(shù)據(jù)進(jìn)行通用的處理,巧用反射,確實(shí)能讓我們達(dá)到事半功倍的效果,能極大的減少重復(fù)代碼,有效解藕,使擴(kuò)展性大大提升。


    關(guān)注我,私信回復(fù)【資料】即可領(lǐng)取視頻中java相關(guān)資料以及一份227頁(yè)最新的bat大廠面試寶典

    最后

    歡迎大家一起交流,喜歡文章記得關(guān)注我點(diǎn)個(gè)贊喲,感謝支持!

    總結(jié)

    以上是生活随笔為你收集整理的怎么实现注解_通透!一口气搞懂注解到底怎么用的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。