不吹牛逼,撸个注解有什么难的
注解是 Java 中非常重要的一部分,但經(jīng)常被忽視也是真的。之所以這么說是因?yàn)槲覀兏鼉A向成為一名注解的使用者而不是創(chuàng)建者。@Override 注解用過吧?@Service 注解用過吧?但你知道怎么自定義一個(gè)注解嗎?
恐怕你會(huì)搖搖頭,擺擺手,不好意思地承認(rèn)自己的確沒有自定義過。
01、注解是什么
注解(Annotation)是在 Java 1.5 時(shí)引入的概念,同 class 和 interface 一樣,也屬于一種類型。注解提供了一系列數(shù)據(jù)用來裝飾程序代碼(類、方法、字段等),但是注解并不是所裝飾代碼的一部分,它對(duì)代碼的運(yùn)行效果沒有直接影響(這句話怎么理解呢?),由編譯器決定該執(zhí)行哪些操作。
來看一段代碼,我隨便寫的,除了打印到控制臺(tái)的那句宣傳語,其他都不重要,嘻嘻。
public?class?AutowiredTest?{@Autowiredprivate?String?name;public?static?void?main(String[]?args)?{System.out.println("沉默王二,一枚有趣的程序員");} }注意到 @Autowired 這個(gè)注解了吧?它本來是為 Spring 容器注入 Bean 的,現(xiàn)在被我無情地扔在了成員變量 name 的身上,但這段代碼所在的項(xiàng)目中并沒有啟用 Spring,意味著 @Autowired 注解此時(shí)只是一個(gè)擺設(shè)。
我之所以舉這個(gè)無聊的例子就是為了證明一個(gè)觀點(diǎn):注解對(duì)代碼的運(yùn)行效果沒有直接影響,明白我的用意了吧?
02、注解的生命周期
注解的生命周期有 3 種策略,定義在 RetentionPolicy 枚舉中。
1)SOURCE:在源文件中有效,被編譯器丟棄。
2)CLASS:在編譯器生成的字節(jié)碼文件中有效,但在運(yùn)行時(shí)會(huì)被處理類文件的 JVM 丟棄。
3)RUNTIME:在運(yùn)行時(shí)有效。這也是注解生命周期中最常用的一種策略,它允許程序通過反射的方式訪問注解,并根據(jù)注解的定義執(zhí)行相應(yīng)的代碼。
03、注解裝飾的目標(biāo)
注解的目標(biāo)定義了注解將適用于哪一種級(jí)別的 Java 代碼上,有些注解只適用于方法,有些只適用于成員變量,有些只適用于類,有些則都適用。
截止到 Java 9,注解的類型一共有 11 種,定義在 ElementType 枚舉中。
1)TYPE:用于類、接口、注解、枚舉
2)FIELD:用于字段(類的成員變量),或者枚舉常量
3)METHOD:用于方法
4)PARAMETER:用于普通方法或者構(gòu)造方法的參數(shù)
5)CONSTRUCTOR:用于構(gòu)造方法
6)LOCAL_VARIABLE:用于變量
7)ANNOTATION_TYPE:用于注解
8)PACKAGE:用于包
9)TYPE_PARAMETER:用于泛型參數(shù)
10)TYPE_USE:用于聲明語句、泛型或者強(qiáng)制轉(zhuǎn)換語句中的類型
11)MODULE:用于模塊
04、開始擼注解
說再多,都不如擼個(gè)注解來得讓人心動(dòng)。擼個(gè)什么樣的注解呢?一個(gè)字段注解吧,它用來標(biāo)記對(duì)象在序列化成 JSON 的時(shí)候要不要包含這個(gè)字段。
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public?@interface?JsonField?{public?String?value()?default?""; }1)JsonField 注解的生命周期是 RUNTIME,也就是運(yùn)行時(shí)有效。
2)JsonField 注解裝飾的目標(biāo)是 FIELD,也就是針對(duì)字段的。
3)創(chuàng)建注解需要用到 @interface 關(guān)鍵字。
4)JsonField 注解有一個(gè)參數(shù),名字為 value,類型為 String,默認(rèn)值為一個(gè)空字符串。
為什么參數(shù)名要為 value 呢?有什么特殊的含義嗎?
當(dāng)然是有的,value 允許注解的使用者提供一個(gè)無需指定名字的參數(shù)。舉個(gè)例子,我們可以在一個(gè)字段上使用 @JsonField(value = "沉默王二"),也可以把 value = 省略,變成 @JsonField("沉默王二")。
那 default "" 有什么特殊含義嗎?
當(dāng)然也是有的,它允許我們?cè)谝粋€(gè)字段上直接使用 @JsonField,而無需指定參數(shù)的名和值。
05、使用注解
是騾子是馬拉出來遛遛,對(duì)吧?現(xiàn)在 @JsonField 注解已經(jīng)擼好了,接下來就到了怎么使用它的環(huán)節(jié)。
假設(shè)有一個(gè)作者類,他有 3 個(gè)字段,分別是 age、name 和 bookName,后 2 個(gè)是必須序列化的字段。
public?class?Writer?{private?int?age;@JsonField("writerName")private?String?name;@JsonFieldprivate?String?bookName;public?Writer(int?age,?String?name,?String?bookName)?{this.age?=?age;this.name?=?name;this.bookName?=?bookName;}//?getter?/?setter@Overridepublic?String?toString()?{return?"Writer{"?+"age="?+?age?+",?name='"?+?name?+?'\''?+",?bookName='"?+?bookName?+?'\''?+'}';} }1)name 上的 @JsonField 注解提供了顯式的字符串值。
2)bookName 上的 @JsonField 注解使用了缺省項(xiàng)。
接下來,我們來編寫序列化類 JsonSerializer,內(nèi)容如下:
public?class?JsonSerializer?{public?static?String?serialize(Object?object)?throws?IllegalAccessException?{Class<?>?objectClass?=?object.getClass();Map<String,?String>?jsonElements?=?new?HashMap<>();for?(Field?field?:?objectClass.getDeclaredFields())?{field.setAccessible(true);if?(field.isAnnotationPresent(JsonField.class))?{jsonElements.put(getSerializedKey(field),?(String)?field.get(object));}}return?toJsonString(jsonElements);}private?static?String?getSerializedKey(Field?field)?{String?annotationValue?=?field.getAnnotation(JsonField.class).value();if?(annotationValue.isEmpty())?{return?field.getName();}?else?{return?annotationValue;}}private?static?String?toJsonString(Map<String,?String>?jsonMap)?{String?elementsString?=?jsonMap.entrySet().stream().map(entry?->?"\""?+?entry.getKey()?+?"\":\""?+?entry.getValue()?+?"\"").collect(Collectors.joining(","));return?"{"?+?elementsString?+?"}";} }JsonSerializer 類的內(nèi)容看起來似乎有點(diǎn)多,但不要怕,我一點(diǎn)點(diǎn)來解釋,直到你搞明白為止。
1)serialize() 方法是用來序列化對(duì)象的,它接收一個(gè) Object 類型的參數(shù)。objectClass.getDeclaredFields() 通過反射的方式獲取對(duì)象聲明的所有字段,然后進(jìn)行 for 循環(huán)遍歷。在 for 循環(huán)中,先通過 field.setAccessible(true) 將反射對(duì)象的可訪問性設(shè)置為 true,供序列化使用(如果沒有這個(gè)步驟的話,private 字段是無法獲取的,會(huì)拋出 IllegalAccessException 異常);再通過 isAnnotationPresent() 判斷字段是否裝飾了 JsonField 注解,如果是的話,調(diào)用 getSerializedKey() 方法,以及獲取該對(duì)象上由此字段表示的值,并放入 jsonElements 中。
2)getSerializedKey() 方法用來獲取字段上注解的值,如果注解的值是空的,則返回字段名。
3)toJsonString() 方法借助 Stream 流的方式返回格式化后的 JSON 字符串。如果對(duì) Stream 流比較陌生的話,請(qǐng)查閱我之前寫的 Stream 流入門。
看完我的解釋,是不是豁然開朗了?
接下來,我們來寫一個(gè)測(cè)試類 JsonFieldTest,內(nèi)容如下:
public?class?JsonFieldTest?{public?static?void?main(String[]?args)?throws?IllegalAccessException?{Writer?cmower?=?new?Writer(18,"沉默王二","Web全棧開發(fā)進(jìn)階之路");System.out.println(JsonSerializer.serialize(cmower));} }程序輸出結(jié)果如下:
{"bookName":"Web全棧開發(fā)進(jìn)階之路","writerName":"沉默王二"}從結(jié)果上來看:
1)Writer 類的 age 字段沒有裝飾 @JsonField 注解,所以沒有序列化。
2)Writer 類的 name 字段裝飾了 @JsonField 注解,并且顯示指定了字符串“writerName”,所以序列化后變成了 writerName。
3)Writer 類的 bookName 字段裝飾了 @JsonField 注解,但沒有顯式指定值,所以序列化后仍然是 bookName。
06、鳴謝
好了,我親愛的讀者朋友,以上就是本文的全部內(nèi)容了,是不是感覺擼個(gè)注解也沒什么難的?你也趕緊動(dòng)動(dòng)小手試試吧!
END
IDEA 終于支持中文版和 JDK 直接下載了(太方便了)附新版介紹視頻6大分布式定時(shí)任務(wù)對(duì)比除了負(fù)載均衡,Nginx 還能干啥?總結(jié)
以上是生活随笔為你收集整理的不吹牛逼,撸个注解有什么难的的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何学会阅读源码?
- 下一篇: 服务端接口中的那些坑