Java 注解知识总结
引言
本博客總結自《Java 編程思想》第 20 章。
一、什么是注解
注解是 Java 5 引入的一種通過反射機制實現的語法特性,開發者可以通過在類、域、方法等元素前面標記一個“標簽”達到對程序的源碼、類信息或運行時進行某種說明或處理的效果,盡可能地簡化代碼,從而使程序開發更高效。但需要注意的是,編譯器要確保在其構造路徑上,必須有對應注解的定義。
Java 中在 1.5 之初內置了三個標準注解,@Deprecated、@Override 、@SupressWarning 。我們經常會在程序的各個角落看到它們。
以@SupressWarning為例,
package java.lang;import java.lang.annotation.*; import static java.lang.annotation.ElementType.*;@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE}) @Retention(RetentionPolicy.SOURCE) public @interface SuppressWarnings {String[] value(); }它一般用于去除不恰當的編譯警告,俗稱“報黃”。隨著Java 慢慢的發展,也逐漸引入了更多的注解,比如在 Java 8 伴隨著 Lambda表達式的加入,而一同入住 Java 大家庭的 @FunctionalInterface 注解:
package java.lang;import java.lang.annotation.*;@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface FunctionalInterface {}?二、如何聲明注解
注解的定義非常類似接口,不同的是,像上面的例子中,我們要在 interface 關鍵字前面加 “@”,以此來聲明這是一個注解。
除此之外,Java 提供了四個元注解:
@Target @Retention @Documented @Inherited其中,@Target 、@Retention 在定義注解時,一般情況下都是必選項。
元注解專職負責注解其他注解。
@Target :表示該注解可以用于什么地方。需要給它傳入一個ElementType 枚舉對象,常用選項有:
?? ?CONSTRUCTOR : 構造器聲明
?? ?FIELD : 域聲明(包括enum實例)
?? ?LOCAL_VARIABLE : 局部變量聲明
?? ?METHOD : 方法聲明
?? ?PACKAGE : 包聲明
?? ?PARAMETER : 參數聲明
?? ?TYPE : 類、接口(包括注解聲明)、enum 聲明
@Retention :表示需要在什么級別保存該注解。需要傳入一個?RetentionPolicy ,可選值:
?? ?SOURCE : 注解將被編譯器丟棄。
?? ?CLASS : 注解在class文件中使用,但會被 JVM 丟棄。
?? ?RUNTIME : vm將會在運行期間也保留該注解,因此可以通過反射機制讀取注解的信息。
@Documented : 將此注解包含在javadoc 中。
@Inherited : 允許子類繼承父類的注解。?
注解定義示例:
package com.mht.demo.注解;import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface MyTest {public int id();public String description() default "no description"; }可以看到,注解的確非常像接口,同時,和其他任何 Java 接口一樣,注解也將會被編譯成為一個 class 文件。
@MyTest 用到了兩個元注解:@Target 、@Retention ,如上所示,@MyTest 只能用于方法上,如果希望自己的注解既可以用于方法上,也可以用于類上,那么可以這樣寫:
@Target({ ElementType.METHOD, ElementType.TYPE })@Retention用于定義你的注解應用在什么級別,源代碼(SOURCE)、類文件(CLASS)、運行時(RUNTIME)。
注解中往往包含一些元素,比如上例中的 id 、description 。
在分析和處理注解時,程序或工具可以利用這些值,它們看起來像接口中的抽象方法,唯一不同的是我們可以為這些元素指定默認值。對于沒有任何元素的注解如 @FunctionalInterface 被稱為標記注解。
編譯器會對注解中的元素進行類型檢查,因此,將這些元素與數據庫相關聯是安全的。注解元素可用的類型有一定的限制,它不允許任何包裝類型。被允許的注解元素類型有:
1、8大基本類型
2、String
3、Class
4、enum
5、Annotion : 嵌套注解,非常有用的技巧
6、以上類型的數組
如果使用了除上述幾種以外的其他類型,那么編譯器就會報錯。 注意,注解的元素值永遠不能為null,要么在聲明元素之初設置默認值,要么就在使用注解時添加該元素值,而且必須是不為 null 的值(如果希望注解中的某個元素是必填項,那么在聲明時就可以不為其指定默認值)。因此,注解處理器無法通過null 來判斷元素是否缺失。為了繞開這個限制,一般會通過 自己定義特殊值來判斷元素是否存在,比如 -1 或 ""。
default 關鍵字來定義元素的默認值,在使用該注解時,如果沒有給出元素的值 那么注解處理器就會使用此元素的默認值。
三、注解的使用與自定義注解處理器
以 @MyTest 為例,我們來看看注解如何使用,以及如何處理這個注解。
public class SomeService {@MyTest(id = 1, description = "Hello Annotation! Hello 2020 !")public void testMyTestFeature() {System.out.println("這是testMyTestFeature()方法!");} }我們定義了一個類,聲明了一個方法,并為其標記我們的 @MyTest 注解。
接下來我們來實現一個注解處理器?MyTestProcessor :
/*** '@MyTest'注解處理器* @author mht**/ public class MyTestProcessor {public static void processMyTest(Class<?> clz) {Method[] declaredMethods = clz.getDeclaredMethods();// getAnnotation() 方法會返回指定類型的注解,如果沒有,則返回nullMyTest myTest = declaredMethods[0].getAnnotation(MyTest.class);// 這里一般都會判斷獲取到的注解是否為空if (myTest != null) {System.out.println("找到標記注解:id:" + myTest.id() + ", 描述:" + myTest.description());}}public static void main(String[] args) {processMyTest(SomeService.class);} }執行 main 方法,測試輸出結果:
找到標記注解:id:1, 描述:Hello Annotation! Hello 2020 !注解處理器雖然名字聽起來很專業,但實際上,我們并不需要為我們處理注解的類或方法繼承或實現什么。正如第一節開始所說的,注解是一種通過反射機制來實現的特性,我們可以通過 Class 對象來獲取我們想要的注解。
像上面的代碼有點過于簡單了,一般情況下,我們可能會為一個目標添加多個注解,因此一般的處理注解的思路就是:
1、獲取類信息(Class 對象可以直接獲取類上的注解對象或注解對象數組)
2、通過?getDeclaredMethods() 等方法,獲取目標信息(有時也有可能是 類或域)
3、通過?getAnnotation(Class<T> annotationClass)、getAnnotations() 等方法,獲取一個或多個待處理的注解對象。
4、通過注解對象,獲取內部元素,根據其值進行邏輯處理。
這是一個一般的注解處理思路,許多框架中的注解處理往往比較復雜,且經常需要配合遍歷來處理多個類信息,多個注解的情況。
值得注意的是,注解往往都是被動的處理,它不能主動發出某種信號傳遞給注解處理器,也就是說,我們必須主動找到這些注解或將攜帶他們的類傳入注解處理器。
某些框架在批量處理注解的時候,就必須為注解處理器指定一個盡可能小的路徑范圍,以此來掃描該路徑下的類信息。
Mybatis 中的 @MapperScan 就是一個很好的例證,如果不為其指定一個掃描路徑,Mybatis 框架就可能必須從 classpath 的根路徑找起,這會非常影響框架處理效率。
綜上就是關于 注解的總結和思考,歡迎文末留言。
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的Java 注解知识总结的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Spring Boot整合Redis——
- 下一篇: Linux 实操———— Shell 远