1. 程式人生 > >深入學習JAVA註解-Annotation(學習過程)

深入學習JAVA註解-Annotation(學習過程)

# JAVA註解-Annotation學習 > 本文目的:專案開發過程中遇到自定義註解,想要弄清楚其原理,但是自己的基礎知識不足以支撐自己去探索此問題,所以先記錄問題,然後補充基礎知識,然後解決其問題。記錄此學習過程。 ## 專案中遇到的註解: ``` //使用註解的地方 @ServiceScan({"com.sinosoft.lis.pubfun"}) public class CodeQuerySQL {} //註解類 ServiceScan @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface ServiceScan { String[] value() default {}; } //這個com.sinosoft.lis.pubfun包下的類 @CodeQuery public interface CodeQuery_Framework {} //註解類CodeQuery @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface CodeQuery { } ``` **問題描述:** 開發中,我們需要自己新建一個codequeryframework_nb類,這個類是這樣使用的,放在com.sinosoft.lis.pubfun包下就行,然後自定義方法,就會自動被掃描到,然後會自動載入定義的介面方法類,去實現我們的查詢下拉的功能。註解使用正確,包放在正確的位置就可以使用了,但是為什麼不會和之前的codequeryframework衝突?具體是怎麼實現的,我們組內的成員都沒搞明白。我決定把註解這個知識點往深處挖一挖。 我們的問題: 1. 功能這是怎麼實現的。 2. 為什麼不會和之前建立的類衝突。 3. 其實就是,怎麼實現的。 學習目的: 1. 能夠解釋:為什麼這個類是通過註解如何實現的 2. 瞭解註解是什麼,註解怎麼用,註解的實現原理 --- ## 註解基礎知識補充: 學習過程: 1. 首先,先找一個資料,大概的對註解有一定的認識。 https://www.runoob.com/w3cnote/java-annotation.html 2. 查詢一些博主的博文,看看他們都提到哪些大的點,基本上都是一樣的,所以就能定位到自己需要首先了解哪些是需要學習和了解的 3. 對於過程中的疑問,進行查漏補缺。為自己解惑 其他人提到的知識點:java5,元註解,自定義註解,註解的實現,註解的屬性,註解的作用,在反射中使用註解 ### 註解的本質 ``` //「java.lang.annotation.Annotation」介面中有這麼一句話,用來描述『註解』。 The common interface extended by all annotation types 所有的註解型別都繼承自這個普通的介面(Annotation) ``` 我們先隨便點開一個JDK內的註解,檢視一下是如何定義的 ``` @Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override { } ``` 這是註解 @Override 的定義,其實它本質上就是: ``` public interface Override extends Annotation{ } ``` 沒錯,註解的本質就是一個繼承了 Annotation 介面的介面。有關這一點,你可以去反編譯任意一個註解類,你會得到結果的。 ### 為什麼要用註解? 這裡找到了兩位博主的解析,感覺簡單易懂很到位。先來一個整體上的認識。 > 在平時不知道我們是否都用過便利貼,在一張紙上寫好幾句話,貼在我們需要的地方.還有一個情況,大多數人都叫我們程式猿(錢多話少死得快),這也是給我們貼了一個標籤。像這兩種情況基本上就是註解。你可以把這兩種情況聯想到程式碼的註解上。比如我們定義了一個方法,這個方法要實現加法的運算,那麼我們就可以定義一個@ADD標籤。表示這個方法就是實現加法的。我們程式設計師一看到這個@ADD,就能很容易理解這個方法是幹嘛的。簡單而言。註解就是對於程式碼中某些鮮活個體的貼上去的一張標籤。簡化來講,註解如同一張標籤。因為,如果你之前還未正式的學習過註解,你就可以把他當成便利貼標籤就好了,這能幫你理解註解的大部分內容。 > 以前,『XML』是各大框架的青睞者,它以鬆耦合的方式完成了框架中幾乎所有的配置,但是隨著專案越來越龐大,『XML』的內容也越來越複雜,維護成本變高。於是就有人提出來一種標記式高耦合的配置方式,『註解』。方法上可以進行註解,類上也可以註解,欄位屬性上也可以註解,反正幾乎需要配置的地方都可以進行註解。關於『註解』和『XML』兩種不同的配置模式,爭論了好多年了,各有各的優劣,註解可以提供更大的便捷性,易於維護修改,但耦合度高,而 XML 相對於註解則是相反的。追求低耦合就要拋棄高效率,追求效率必然會遇到耦合。本文意不再辨析兩者誰優誰劣,而在於以最簡單的語言介紹註解相關的基本內容。 ### 元註解 ##### 元註解的作用: 『元註解』是用於修飾註解的註解,通常用在註解的定義上 ``` @Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override { } ``` 這是我們 @Override 註解的定義,你可以看到其中的 @Target,@Retention 兩個註解就是我們所謂的『元註解』,『元註解』一般用於指定某個註解生命週期以及作用目標等資訊。 ##### 那麼元註解分別有哪些 ``` //目前jdk官方提供的元註解有4個 @Target:定義註解的作用目標 @Retention:定義註解的生命週期 @Documented:定義註解是否應當被包含在 JavaDoc 文件中 @Inherited:定義是否允許子類繼承該註解 ``` ##### 元註解詳解 1. **@Target** -用於指明被修飾的註解最終可以作用的目標是誰,也就是指明,你的註解到底是用來修飾方法的?修飾類的?還是用來修飾字段屬性的。**Target 的定義如下:** ``` @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Target { /** * Returns an array of the kinds of elements an annotation type * can be applied to. * @return an array of the kinds of elements an annotation type * can be applied to */ ElementType[] value(); } ``` 我們可以通過以下的方式來為這個 value 傳值: ``` @Target(value = {ElementType.FIELD}) ``` > 被這個 @Target 註解修飾的註解將只能作用在成員欄位上,不能用於修飾方法或者類。其中,ElementType 是一個列舉型別,有以下一些值: ``` public enum ElementType { /** Class, interface (including annotation type), or enum declaration */ TYPE, //允許被修飾的註解作用在類、介面和列舉上 /** Field declaration (includes enum constants) */ FIELD, //允許作用在屬性欄位上 /** Method declaration */ METHOD, //允許作用在方法上 /** Formal parameter declaration */ PARAMETER, //允許作用在方法引數上 /** Constructor declaration */ CONSTRUCTOR, //允許作用在構造器上 /** Local variable declaration */ LOCAL_VARIABLE, //允許作用在本地區域性變數上 /** Annotation type declaration */ ANNOTATION_TYPE, //允許作用在註解上 /** Package declaration */ PACKAGE, //允許作用在包上 /** * Type parameter declaration * 表示該註解能寫在型別變數的宣告語句中(如:泛型宣告)。 * @since 1.8 */ TYPE_PARAMETER, /** * Use of a type * 表示該註解能寫在使用型別的任何語句中。 * @since 1.8 */ TYPE_USE } 注意:上述中文翻譯為自己翻譯的,如果有錯誤,請自行查閱官方文件 最後從jdk1.8新增的兩個列舉型別的作用,是通過搜尋網路資料查詢得來 型別註解: JDK1.8之後,關於元註解@Target的引數型別ElementType列舉值多了兩個: TYPE_PARAMETER和TYPE_USE。 在Java8之前,註解只能是在宣告的地方所使用,Java8開始,註解可以應用在任何地方。 ElementType.TYPE_PARAMETER 表示該註解能寫在型別變數的宣告語句中(如:泛型宣告)。 ElementType.TYPE_USE 表示該註解能寫在使用型別的任何語句中。 ``` 2. @Retention - 標識這個註解怎麼儲存,是隻在程式碼中,還是編入class檔案中,或者是在執行時可以通過反射訪問。**它的基本定義如下:** ``` @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Retention { /** * Returns the retention policy. * @return the retention policy */ RetentionPolicy value(); } ``` 同樣的,它也有一個 value 屬性: ``` @Retention(value = RetentionPolicy.RUNTIME ``` 這裡的 RetentionPolicy 依然是一個列舉型別,它有以下幾個列舉值可取: ``` public enum RetentionPolicy { /** * Annotations are to be discarded by the compiler. */ SOURCE, //當前註解編譯期可見,不會寫入 class 檔案 /** * Annotations are to be recorded in the class file by the compiler * but need not be retained by the VM at run time. This is the default * behavior. */ CLASS, //類載入階段丟棄,會寫入 class 檔案 /** * Annotations are to be recorded in the class file by the compiler and * retained by the VM at run time, so they may be read reflectively. * * @see java.lang.reflect.AnnotatedElement */ RUNTIME //永久儲存,可以反射獲取 } ``` > @Retention 註解指定了被修飾的註解的生命週期,一種是隻能在編譯期可見,編譯後會被丟棄,一種會被編譯器編譯進class檔案中,無論是類或是方法,乃至欄位,他們都是有屬性表的,而 JAVA 虛擬機器也定義了幾種註解屬性表用於儲存註解資訊,但是這種可見性不能帶到方法區,類載入時會予以丟棄,最後一種則是永久存在的可見性。 #### 如何驗證生命週期? ``` @Target(ElementType.TYPE) @Retention(RetentionPolicy.CLASS) public @interface TestAnnotation { } @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface TestAnnotation2 { } @Target(ElementType.TYPE) @Retention(RetentionPolicy.SOURCE) public @interface TestAnnotation3 { } ``` ``` @TestAnnotation @TestAnnotation2 @TestAnnotation3 public class TestJava { public static void main(String[] args) throws ClassNotFoundException { Class testJava = Class.forName("com.sinosoft.lis.pubfun.TestJava"); Annotation[] annotations = testJava.getAnnotations(); for (Annotation annotation : annotations) { System.out.println(annotation.annotationType()); } } ``` > 你明白我的意思吧。 3. @Inherited - 標記這個註解是繼承於哪個註解類(預設 註解並沒有繼承於任何子類). 4. @Documented - 標記這些註解是否包含在使用者文件中. > 剩下兩種型別的註解我們日常用的不多,也比較簡單,這裡不再詳細的進行介紹了,只需要知道他們各自的作用即可. @Documented 註解修飾的註解,當我們執行 JavaDoc 文件打包時會被儲存進 doc 文件,反之將在打包時丟棄. @Inherited 註解修飾的註解是具有可繼承性的,也就說我們的註解修飾了一個類,而該類的子類將自動繼承父類的該註解. ### JAVA提供的三大內建註解 ``` #### 除了上述四種元註解外,JDK 還為我們預定義了另外三種註解,它們是: 1. @Override 2. @Deprecated 3. @SuppressWarnings ``` ##### JAVA提供的三大內建註解-詳解 **1. @Override** 註解想必是大家很熟悉的了,標記為方法為重寫,它的定義如下: ``` /** * Indicates that a method declaration is intended to override a * method declaration in a supertype. If a method is annotated with * this annotation type compilers are required to generate an error * message unless at least one of the following conditions hold: * *
  • * The method does override or implement a method declared in a * supertype. *
  • * The method has a signature that is override-equivalent to that of * any public method declared in {@linkplain Object}. *
* * @author Peter von der Ahé * @author Joshua Bloch * @jls 9.6.1.4 @Override * @since 1.5 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override { } ``` >
它沒有任何的屬性,所以並不能儲存任何其他資訊。它只能作用於方法之上,編譯結束後將被丟棄。所以你看,它就是一種典型的『標記式註解』,僅被編譯器可知,編譯器在對 java 檔案進行編譯成位元組碼的過程中,一旦檢測到某個方法上被修飾了該註解,就會去匹對父類中是否具有一個同樣方法簽名的函式,如果不是,自然不能通過編譯。 **2. @Deprecated :** 主要用來標記該Element已經過時,基本定義如下 ``` /** * A program element annotated @Deprecated is one that programmers * are discouraged from using, typically because it is dangerous, * or because a better alternative exists. Compilers warn when a * deprecated program element is used or overridden in non-deprecated code. * * @author Neal Gafter * @since 1.5 * @jls 9.6.3.6 @Deprecated */ @Documented @Retention(RetentionPolicy.RUNTIME) @Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE}) public @interface Deprecated { } ``` >
依然是一種『標記式註解』,永久存在,可以修飾所有的型別,作用是,標記當前的類或者方法或者欄位等已經不再被推薦使用了,可能下一次的 JDK 版本就會刪除。當然,編譯器並不會強制要求你做什麼,只是告訴你 JDK 已經不再推薦使用當前的方法或者類了,建議你使用某個替代者。 **3. @SuppressWarnings**:主要用來壓制 java 的警告,它的基本定義如下: ``` /** * Indicates that the named compiler warnings should be suppressed in the * annotated element (and in all program elements contained in the annotated * element). Note that the set of warnings suppressed in a given element is * a superset of the warnings suppressed in all containing elements. For * example, if you annotate a class to suppress one warning and annotate a * method to suppress another, both warnings will be suppressed in the method. * *

As a matter of style, programmers should always use this annotation * on the most deeply nested element where it is effective. If you want to * suppress a warning in a particular method, you should annotate that * method rather than its class. * * @author Josh Bloch * @since 1.5 * @jls 4.8 Raw Types * @jls 4.12.2 Variables of Reference Type * @jls 5.1.9 Unchecked Conversion * @jls 5.5.2 Checked Casts and Unchecked Casts * @jls 9.6.3.5 @SuppressWarnings */ @Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE}) @Retention(RetentionPolicy.SOURCE) public @interface SuppressWarnings { /** * The set of warnings that are to be suppressed by the compiler in the * annotated element. Duplicate names are permitted. The second and * successive occurrences of a name are ignored. The presence of * unrecognized warning names is not an error: Compilers must * ignore any warning names they do not recognize. They are, however, * free to emit a warning if an annotation contains an unrecognized * warning name. * *

The string {@code "unchecked"} is used to suppress * unchecked warnings. Compiler vendors should document the * additional warning names they support in conjunction with this * annotation type. They are encouraged to cooperate to ensure * that the same names work across multiple compilers. * @return the set of warnings to be suppressed */ String[] value(); } ``` 它有一個 value 屬性需要你主動的傳值,這個 value 代表一個什麼意思呢,這個 value 代表的就是需要被壓制的警告型別。例如: ``` public static void main(String[] args) { Date date = new Date(2019, 12, 27); } ``` 這麼一段程式碼,程式啟動時編譯器會報一個警告。 > Warning:(8, 21) java: java.util.Date 中的 Date(int,int,int) 已過時 而如果我們不希望程式啟動時,編譯器檢查程式碼中過時的方法,就可以使用 @SuppressWarnings 註解並給它的 value 屬性傳入一個引數值來壓制編譯器的檢查。 ``` @SuppressWarning(value = "deprecated") public static void main(String[] args) { Date date = new Date(2019, 12, 27); } ``` 這樣你就會發現,編譯器不再檢查 main 方法下是否有過時的方法呼叫,也就壓制了編譯器對於這種警告的檢查。 當然,JAVA 中還有很多的警告型別,他們都會對應一個字串,通過設定 value 屬性的值即可壓制對於這一類警告型別的檢查。 ### 自定義註解: 自定義註解的語法比較簡單,通過類似以下的語法即可自定義一個註解。 ``` public @interface InnotationName{ } ``` > 當然,自定義註解的時候也可以選擇性的使用元註解進行修飾,這樣你可以更加具體的指定你的註解的生命週期、作用範圍等資訊。 ### 註解的屬性 && 註解的使用 註解的屬性也叫做成員變數。註解只有成員變數,沒有方法。註解的成員變數在註解的定義中以“無形參的方法”形式來宣告,其方法名定義了該成員變數的名字,其返回值定義了該成員變數的型別。 ``` @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface TestAnnotation { int id(); String msg(); } ``` 上面程式碼定義了 TestAnnotation 這個註解中擁有 id 和 msg 兩個屬性。在使用的時候,我們應該給它們進行賦值。 賦值的方式是在註解的括號內以 value=”” 形式,多個屬性之前用 ,隔開。 需要注意的是,在註解中定義屬性時它的型別必須是 8 種基本資料型別外加 類、介面、註解及它們的陣列。 註解中屬性可以有預設值,預設值需要用 default 關鍵值指定。比如: ``` @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface TestAnnotation { public int id() default -1; public String msg() default "Hi"; } ``` TestAnnotation 中 id 屬性預設值為 -1,msg 屬性預設值為 Hi。 **它可以這樣應用。** ``` @TestAnnotation() public class Test {} ``` 因為有預設值,所以無需要再在 @TestAnnotation 後面的括號裡面進行賦值了,這一步可以省略。 最後,還需要注意的一種情況是一個註解沒有任何屬性。比如 ``` public @interface Perform {} ``` 那麼在應用這個註解的時候,括號都可以省略。 --- #### 到目前為止:我僅僅知道註解是如何定義的,具體用起來是怎麼實現的呢? 1. 比如@override是怎麼去校驗的??畢竟點開原始碼,看它定義起來挺簡單的 ``` @Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override { } ``` 2. 再比如,我的類上面新增一個 @Documented 註解,在生成文件的時候就會自動根據我寫的 doc去生成文件嗎?他是怎麼實現的?通過掃描註解類來完成嗎? 3. 再比如,之前用過的@bean 註解,我們在spring框架使用時候,在java類上定義之後,就會在載入的時候掃描載入到容器嗎?具體是怎麼實現的呢? 4. 我覺得我需要很明白的理解這寫問題。自己預估可能跟其他人提到的反射有關 #### 那麼。帶著這些個疑問,我們繼續向下學習。 --- ### 註解與反射 上述內容我們介紹了註解使用上的細節,也簡單提到,「註解的本質就是一個繼承了 Annotation 介面的介面」,現在我們就來從虛擬機器的層面看看,註解的本質到底是什麼。 ### 註解的使用例項 註解運用的地方太多了,如: JUnit 這個是一個測試框架,典型使用方法如下: ``` public class ExampleUnitTest { @Test public void addition_isCorrect() throws Exception { assertEquals(4, 2 + 2); } } ``` 還有例如ssm框架,springboot,springcloud等運用了大量的註解。 ## 總結 算是對註解有了基本的認知。談談自我總結吧。 1. 如果註解難於理解,你就把它類同於標籤,標籤為了解釋事物,註解為了解釋程式碼。 2. 註解的基本語法,建立如同介面,但是多了個 @ 符號。 3. 註解的元註解。 4. 註解的屬性。 5. 註解主要給編譯器及工具型別的軟體用的。 6. 註解的提取需要藉助於 Java 的反射技術,反射比較慢,所以註解使用時也需要謹慎計較時間成本(需研究反射,!important)。 我之前的問題: 1. 我推論,我之前的問題,並不是出現在註解上面了。 2. 我之前的疑問的功能是通過註解,反射來完成的.註解只是起到了註解該完成的功能。 3. 接下來需要研究的是:反射。 通過反射機制會掃描出所有被@CodeQuery 修飾過的類或者介面並以bean物件的形式注入到自己的容器中來統一管理,根據被@CodeQuery修飾的介面或者類,就可以確定了被@CodeQuery修飾過得類都有哪些,遍歷所有Class檔案,然後可以用反射中的Method類來獲取所有被@SQL修飾過的方法的名字,通過方法名字就可以在程式執行時呼叫對應的介面來執行sql語句了 --- #### 參考文獻 : - https://www.cnblogs.com/yangming1996/p/9295168.html - https://www.cnblogs.com/love-menglong/p/11165469.html - https://www.runoob.com/w3cnote/java-annotation.html - https://blog.csdn.net/tainxiawuti/article/details/99644352 - https://www.cnblogs.com/skywang12345/ 《大佬》 #### 擴充套件作業 1. Class原始碼查閱並瞭解裡面的內建方法,如裡面提供的有檢視反射類的註解方法: ``` Class testJavaClass = TestJava.class; testJavaClass.getAnnotations(); ``` 2. 認識到了底層知識的重要性,感受到了Java底層的力量。 3. 反射的理論知識,實際應用,應用場景分析。