註解是 Java 中非常重要的一部分,但經常被忽視也是真的。之所以這麼說是因為我們更傾向成為一名註解的使用者而不是創建者。@Override 註解用過吧?@Service 註解用過吧?但你知道怎麼自定義一個註解嗎?
恐怕你會搖搖頭,擺擺手,不好意思地承認自己的確沒有自定義過。
01、註解是什麼
註解(Annotation)是在 Java 1.5 時引入的概念,同 class 和 interface 一樣,也屬於一種類型。註解提供了一系列數據用來裝飾程序代碼(類、方法、字段等),但是註解並不是所裝飾代碼的一部分,它對代碼的運行效果沒有直接影響(這句話怎麼理解呢?),由編譯器決定該執行哪些操作。
來看一段代碼,我隨便寫的,除了打印到控制檯的那句宣傳語,其他都不重要,嘻嘻。
<code>public
class
AutowiredTest
{ @Autowiredprivate
String name;public
static
void
main
(String[] args
) { System.out
.println("沉默王二,一枚有趣的程序員"
); } }/<code>
注意到 @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 的時候要不要包含這個字段。
<code>public
JsonField {public
String value()default
""
; }/<code>
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 個是必須序列化的字段。
<code>public
class
Writer
{private
int age;private
String name;private
String bookName;public
Writer(int age, String name, String bookName) {this
.age = age;this
.name = name;this
.bookName = bookName; }public
String toString() {return
"Writer{"
+"age="
+ age +", name='"
+ name +'\''
+", bookName='"
+ bookName +'\''
+'}'
; } }/<code>
1)name 上的 @JsonField 註解提供了顯式的字符串值。
2)bookName 上的 @JsonField 註解使用了缺省項。
接下來,我們來編寫序列化類 JsonSerializer,內容如下:
<code>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 +"}"
; } }/<code>
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,內容如下:
<code>public
class
JsonFieldTest
{public
static
void
main
(String[] args
) throws IllegalAccessException { Writer cmower =new
Writer(18
,"沉默王二"
,"Web全棧開發進階之路"
); System.out
.println(JsonSerializer.serialize(cmower)); } }/<code>
程序輸出結果如下:
<code>{"bookName"
:"Web全棧開發進階之路"
,"writerName"
:"沉默王二"
} /<code>
從結果上來看:
1)Writer 類的 age 字段沒有裝飾 @JsonField 註解,所以沒有序列化。
2)Writer 類的 name 字段裝飾了 @JsonField 註解,並且顯示指定了字符串“writerName”,所以序列化後變成了 writerName。
3)Writer 類的 bookName 字段裝飾了 @JsonField 註解,但沒有顯式指定值,所以序列化後仍然是 bookName。
作者:沉默王二
鏈接:https://juejin.im/post/5e911eca6fb9a03c485777d6