不吹牛逼,擼個註解有什麼難的

註解是 Java 中很是重要的一部分,但常常被忽視也是真的。之因此這麼說是由於咱們更傾向成爲一名註解的使用者而不是建立者。@Override 註解用過吧?@Service 註解用過吧?但你知道怎麼自定義一個註解嗎?java

恐怕你會搖搖頭,擺擺手,很差意思地認可本身的確沒有自定義過。git

0一、註解是什麼

註解(Annotation)是在 Java 1.5 時引入的概念,同 class 和 interface 同樣,也屬於一種類型。註解提供了一系列數據用來裝飾程序代碼(類、方法、字段等),可是註解並非所裝飾代碼的一部分,它對代碼的運行效果沒有直接影響(這句話怎麼理解呢?),由編譯器決定該執行哪些操做。程序員

來看一段代碼,我隨便寫的,除了打印到控制檯的那句宣傳語,其餘都不重要,嘻嘻。github

public class AutowiredTest {
    @Autowired
    private String name;

    public static void main(String[] args) {
        System.out.println("沉默王二,一枚有趣的程序員");
    }
}
複製代碼

注意到 @Autowired 這個註解了吧?它原本是爲 Spring 容器注入 Bean 的,如今被我無情地扔在了成員變量 name 的身上,但這段代碼所在的項目中並無啓用 Spring,意味着 @Autowired 註解此時只是一個擺設。面試

我之因此舉這個無聊的例子就是爲了證實一個觀點:註解對代碼的運行效果沒有直接影響,明白個人用意了吧?json

0二、註解的生命週期

註解的生命週期有 3 種策略,定義在 RetentionPolicy 枚舉中。bash

1)SOURCE:在源文件中有效,被編譯器丟棄。微信

2)CLASS:在編譯器生成的字節碼文件中有效,但在運行時會被處理類文件的 JVM 丟棄。ide

3)RUNTIME:在運行時有效。這也是註解生命週期中最經常使用的一種策略,它容許程序經過反射的方式訪問註解,並根據註解的定義執行相應的代碼。測試

0三、註解裝飾的目標

註解的目標定義了註解將適用於哪種級別的 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:用於模塊

0四、開始擼註解

說再多,都不如擼個註解來得讓人心動。擼個什麼樣的註解呢?一個字段註解吧,它用來標記對象在序列化成 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,而無需指定參數的名和值。

0五、使用註解

是騾子是馬拉出來遛遛,對吧?如今 @JsonField 註解已經擼好了,接下來就到了怎麼使用它的環節。

假設有一個做者類,他有 3 個字段,分別是 age、name 和 bookName,後 2 個是必須序列化的字段。

public class Writer {
    private int age;

    @JsonField("writerName")
    private String name;

    @JsonField
    private String bookName;

    public Writer(int age, String name, String bookName) {
        this.age = age;
        this.name = name;
        this.bookName = bookName;
    }

    // getter / setter

    @Override
    public 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。

0六、鳴謝

好了,我親愛的讀者朋友,以上就是本文的所有內容了,是否是感受擼個註解也沒什麼難的?你也趕忙動動小手試試吧!原創不易,莫要白票,請你爲本文點贊個吧,這將是我寫做更多優質文章的最強動力。

若是以爲文章對你有點幫助,請微信搜索「 沉默王二 」第一時間閱讀,回覆【666】更有我爲你精心準備的 500G 高清教學視頻(已分門別類)。本文 GitHub 已經收錄,有大廠面試完整考點,歡迎 Star。

相關文章
相關標籤/搜索