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