深入淺出註解,框架設計的基礎
來自專欄程序之心20 人贊了文章
何為註解
註解(Annotation)是 Java 中的一個類型,通俗地理解就像一個標籤,貼在了代碼上。眾所周知,Spring 支持大量註解,基於註解可以完成 Bean 的注入和生命周期管理,註解也是取代 xml 配置的一種方法。使用註解可以把元數據和源代碼綁定在一起,可以用於描述代碼無法描述的信息,並在編譯或運行中使用。
在 Java 中使用註解非常簡單,比如我們可以定義一個用於標記類型的註解,並根據這個註解從 Spring 中提取出相應的 Bean。在這段代碼中,我們使用了 Java 自帶的註解 @Retention、@Override,Spring 提供的註解 @Service、@PostConstruct,以及自定義的註解 @MyAnnotation。
@Service@MyAnnotationpublic class TestAnnotation implements ApplicationContextAware { ApplicationContext context; //定義一個註解 @Retention(RetentionPolicy.RUNTIME) @interface MyAnnotation{} @Override public void setApplicationContext(ApplicationContext context) throws BeansException { this.context = context; } @PostConstruct private void init(){ Map<String, Object> beans = context.getBeansWithAnnotation(MyAnnotation.class); System.out.println("找到 bean " + beans.keySet()); }}
定義註解
定義註解非常像定義介面,在 interface 關鍵詞前面加了一個 @。實際上,註解類型與其他類型一樣,最終也是編譯成 class 文件。定義註解需要用到一些元註解,這些元註解是 Java 語言提供的,用來定義註解的一些特性。
元註解 @Inherited 用於聲明該註解支持子類繼承父類中的註解。
元註解 @Documented 用於聲明該註解需要包含到 Javadoc 中。
元註解 @Retention 定義註解的級別,可選值定義在 RetentionPolicy 枚舉類,包括運行時(RUNTIME)、類文件(CLASS)、源代碼(SOURCE)。
元註解 @Target 定義註解的使用範圍,值的類型是 ElementType 枚舉值。註解的使用範圍可以設置一個或多個,常用的有 ElementType.TYPE 表示用於 Java 類、ElementType.METHOD 表示用於 Java 方法、ElementType.FIELD 表示用於類的欄位、ElementType.PARAMETER 表示用於方法的參數。
註解支持定義元素,定義的形式有點類似方法定義,並可以使用 default 設置默認值。如下代碼中定義的值為 value,獲取值調用 value() 即可,如果使用時沒有給出值則默認值為 test。需要注意的是註解的默認值不能是 null,最終一定會有一個值。如果需要表示值不存在,需要自己定義一個特殊值來表示。
註解的元素類型支持基本類型、String、Class、enum、Annotation 以及這些類型的數組。
//定義一個註解@Inherited@Documented@Target({ElementType.TYPE,ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)public @interface MyAnnotation2 { String value() default "test";}
從 Java 8 開始,新增了元註解 @Repeatable,用於表示註解可以有多個值。如下的代碼 SingleAnnotation 可以有多個值,使用 @Repeatable 聲明了多個值合併為註解 @RepeatAnnotation。這種情況下,@RepeatAnnotation 的元素必須有 value,且類型必須為 SingleAnnotation 數組,才能進行聚合。
使用時,@SingleAnnotation 可以定義多次給出多個不同的值,解析註解時則需要按照 @RepeatAnnotation 進行解析。關於註解的解析,請參考後文註解處理器。
@Retention(RetentionPolicy.RUNTIME)@interface RepeatAnnotation { SingleAnnotation[] value();}@Retention(RetentionPolicy.RUNTIME)@Repeatable(RepeatAnnotation.class)@interface SingleAnnotation{ String value();}@SingleAnnotation("hello")@SingleAnnotation("world")public class RepeatAnnotationTest{ public static void main(String[] args){ RepeatAnnotation annotation = RepeatAnnotationTest.class.getAnnotation(RepeatAnnotation.class); for (SingleAnnotation singleAnnotation : annotation.value()) { System.out.println(singleAnnotation.value()); } }}
Java 中的註解不支持繼承,因此不能使用 extends 關鍵字。但是註解可以進行嵌套,也就是一個註解的值是另一個註解。如下的代碼中,MyAnnotation3 註解的第二個值類型為 MyAnnotation2,使用時對第二個值賦值一樣需要按照註解的格式寫為 @MyAnnotation2("world")。
public @interface MyAnnotation3 { String value() default "test"; MyAnnotation2 annotation();}@MyAnnotation3(value = "hello" ,annotation = @MyAnnotation2("world"))private void test2(){}
註解處理器
定義註解以後需要編寫註解處理器,才能發揮註解的作用。在最開始的例子中,我們定義的註解處理器從 Spring 上下文中找出所有使用了註解 MyAnnotation 的 Java Bean,然後列印出這些 Bean 的名字。
@PostConstructprivate void init(){ Map<String, Object> beans = context.getBeansWithAnnotation(MyAnnotation.class); System.out.println("找到 bean " + beans.keySet());}
在 Java 中可以通過反射很方便地處理註解。對於 Class 上面的註解,可以通過 Class 的方法獲取所有註解或指定註解,如下所示。
//獲取類型的所有註解TestAnnotation.class.getAnnotations();TestAnnotation.class.getDeclaredAnnotations();//獲取類型的指定註解TestAnnotation.class.getAnnotation(MyAnnotation.class);TestAnnotation.class.getDeclaredAnnotation(MyAnnotation.class);
對於方法上的註解,可以通過 Method 的方法獲取所有註解或指定註解,如下所示。Method 還提供了 getParameterAnnotations() 來獲取方法參數上面的所有註解,每個參數的註解都是一個數組,返回值為二維數組。
Method init = TestAnnotation.class.getDeclaredMethod("init");//獲取方法的所有註解init.getAnnotations();init.getDeclaredAnnotations();//獲取方法的指定註解init.getAnnotation(PostConstruct.class);init.getDeclaredAnnotation(PostConstruct.class);//獲取方法的參數的所有註解,返回二維數組init.getParameterAnnotations();
對註解進行處理時,註解可以當做普通的變數對待。如下代碼所示,annotation 是一個註解類型的變數,與普通變數區別不大,可以有類型定義,值也可以是 null。但是讀取註解的值時,需要調用方法,如 annotation.value()。
MyAnnotation2 annotation = TestAnnotation.class.getAnnotation(MyAnnotation2.class);if(annotation != null){ System.out.println(annotation.value());}
結語
關於 Java 註解,本文只覆蓋這些內容。註解是一個被廣泛使用的特性,如 Java 本身的 @Override、Spring 中的 @Service @Autowired、MyBatis 中的 @Insert @Update 等。基於註解,實現了對代碼的自動化處理,如掃描和自動注入 JavaBean,大大簡化了 Java 程序員的開發工作。正因如此,我們不僅需要了解常用的註解使用方式,還需要深入理解註解背後的工作原理。
本文首發於公眾號程序之心,每天給你誠意滿滿的乾貨,歡迎關注。
關於JSP 表達式語言的全面解讀!
Java 虛擬機的關機方式
每分鐘訪問10萬+,11種策略教你保持億級流量網站穩定性!
通俗理解 KMP 字元串匹配演算法
深入理解 Java 枚舉類型,這篇文章就夠了
【Java技術】盤點 Java 中的隊列
MyBatis 動態 SQL 常用功能
Java 9 新增的 3 個語言新特性
推薦閱讀:
※雜文筆記: 「堅持」的背後
※四步處理一個數據或元素的拖放
※妙用SEARCHB函數,直接從字元中提取出數字,既方便又簡單
※一個計科小白的每日編程打卡18.07.25
※史上最清晰的「Android觸摸事件傳遞機制」圖解,一張圖全懂了!