java註解
用法
一個簡單的註解java
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Test1 { String name() default "123"; String[] name2() default {"123","342432","321321321"}; } // public class Test2{ @Test1(name = "2313122313",name2 = {"312231321","4342","65456543"}) public static void sayHello(){ System.out.println("123"); } public static void main(String[] args){ sayHello(); } }
有幾個地方須要注意一下,首先@interface是編譯器識別的,標明該類型是一個繼承了java.lang.annotation.Annotation接口的子接口,稱之爲註解。@Target和@Retention都是jdk中定義好的註解,前者主要標明該註解能夠用於修飾什麼,然後者主要肯定該註解保留的環境,便可以在哪些環境中運行,是編譯期,運行期仍是編寫代碼的時候。程序員
public enum ElementType { TYPE, // Class, interface (including annotation type), or enum declaration FIELD, // Field declaration (includes enum constants) METHOD, // Method declaration PARAMETER, // Formal parameter declaration CONSTRUCTOR, // Constructor declaration LOCAL_VARIABLE, // Local variable declaration ANNOTATION_TYPE, //Annotation type declaration PACKAGE, // Package declaration TYPE_PARAMETER, // type parameter declaration TYPE_USE // Use of a type } public enum RetentionPolicy { SOURCE, CLASS, RUNTIME }
上面兩個枚舉類型分別是註解保留時機(編碼期,編譯期,運行期)和做用範圍,也是註解最爲核心的屬性。spring
不過這樣的接口是沒有意義的。shell
讓註解變得有意義
注意,剛剛咱們說到了,@interface標明該對象是一個繼承自Annotation的子接口,那麼咱們首先得看看該接口的源碼springboot
public interface Annotation { boolean equals(Object obj); int hashCode(); String toString(); Class<? extends Annotation> annotationType(); }
前三個方法比較基礎,第四個函數究竟表達的是啥呢?我剛纔說,@interface標明該對象是繼承了Annotation的子接口,而沒有說是實現了Annotation接口的類,從這裏就能夠看出來,若是實現類,而@interface沒法自動辨別怎麼去實現第四個方法,因此只能是接口,從extends關鍵字也能夠看出來,由@interface修飾的對象就是Annotation的子接口。函數
只不過與通常的接口不一樣的是,註解類型,咱們能夠在定義函數的時候能夠設置默認值,經過default關鍵字實現,並且,註解這樣的接口是沒有實現類的。編碼
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Test1 { String name() default "123"; String[] name2() default {"123","342432","321321321"}; }
我看了幾篇博客,都說註解就是元數據,能夠理解爲程序正常運行的配置文件,能夠定義程序運行的前後關係,配置等。那麼咱們看看其他幾個元註解的實現url
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Documented { } @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Inherited { } @Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface Retention { RetentionPolicy value(); }
能夠看見@Document註解的源碼和@Inherited註解的源碼如出一轍,可是咱們知道這兩個註解的語義是不同的,編譯器是怎麼知道兩個不一樣的註解定義的予以的呢?這是由於jdk內置的註解,編譯器是有一套對應的方法的,也就是編譯器內部自身在掃描註解的時候,對於內置註解,會根據名字去識別和作行爲判斷。.net
因此對於自定義註解,編譯器是沒法識別的,因此自定義註解通常都是做用於運行期,也就是RetentionPolicy.RUNTIME,在編譯期編譯進字節碼。在運行期,須要咱們經過反射技術,識別該註解以及它所攜帶的信息,而後作相應的處理。debug
這就帶來了一個問題,因爲使用的是反射技術,因此,註解帶來的問題,只能在運行期才能發現,同時,這樣也給debug帶來了難度。
運行期如何找到並解析
來看下面這個簡單的例子
// Test1.java 定義註解 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Test1 { String name() default "123"; String[] name2() default {"123","342432","321321321"}; } // Test2.java 定義被註解修飾的方法,一個修改了註解的默認值,一個沒有修改註解的默認值 public class Test2{ @Test1(name = "2313122313",name2 = {"312231321","4342","65456543"}) public static void change(){ } @Test1 public static void notChange(){ } } // Test3.java 用反射尋找被註解修飾的方法 public class Test3 { public static void parsing(Object obj) { if (Objects.isNull(obj)) return; Method[] methods = obj.getClass().getDeclaredMethods(); for (Method method : methods) { if (method.isAnnotationPresent(Test1.class)) { Test1 test1 = method.getAnnotation(Test1.class); System.out.println("methodName = " + method.getName()); System.out.println("name = " + test1.name()); for (String s : test1.name2()) { System.out.println("name2 = " + s); } } } } } // Test4.java 調用 public class Test4{ public static void main(String[] args){ Test2 test2 = new Test2(); Test3.parsing(test2); } }
運行結果以下
methodName = notChange name = 123 name2 = 123 name2 = 342432 name2 = 321321321 methodName = change name = 2313122313 name2 = 312231321 name2 = 4342 name2 = 65456543
從上面這個例子,能夠發現,jdk實現的反射方法中提供了跟註解有關的方法,這樣就方便了開發者找到註解修飾的對象並利用當前註解的值狀況作一些處理,從而讓代碼能夠自動化運行(即經過簡單的配置使得代碼可以正常運行)。
本質上來說,我我的以爲註解是能夠有替換的操做,只不過註解是從設計思想上的提高,使得實現更爲簡單。
帶來的好處
註解和註釋是不一樣的,被註釋的內容只存在於編碼期,編譯器會忽略掉全部的註釋。不一樣地,編譯器會根據註解的保留期標記來肯定是否將註解編譯進字節碼中。註解給程序的靈活性帶來了巨大的變革,這也是爲啥springboot的出現打破了java程序員被xml支配的恐懼的平常,甚至我我的認爲這間接致使了java程序員的內卷(springboot是直接緣由)。更爲複雜的是,知其因此然的程序員更少,但願我能堅持下去,瞭解jdk底層。
炒雞辣雞原創文章,轉載請註明來源