对java注解的深入理解
什么是注解,注解的出現是要解決什么問題?
使用Annotation之前(甚至在使用之后),XML被廣泛的應用于描述元數據。不知何時開始一些應用開發人員和架構師發現XML的維護越來越糟糕了。他們希望使用一些和代碼緊耦合的東西,而不是像XML那樣和代碼是松耦合的(在某些情況下甚至是完全分離的)代碼描述。如果你在Google中搜索“XML vs. annotations”,會看到許多關于這個問題的辯論。最有趣的是XML配置其實就是為了分離代碼和配置而引入的。上述兩種觀點可能會讓你很疑惑,兩者觀點似乎構成了一種循環,但各有利弊。下面我們通過一個例子來理解這兩者的區別。
Annotation本質上就是元數據,誕生的目的就是在java中做標記。
XML vs. annotations
假如你想為應用設置很多的常量或參數,這種情況下,XML是一個很好的選擇,因為它不會同特定的代碼相連。如果你想把某個方法聲明為服務,那么使用Annotation會更好一些,因為這種情況下需要注解和方法緊密耦合起來,開發人員也必須認識到這點。
另一個很重要的因素是Annotation定義了一種標準的描述元數據的方式。在這之前,開發人員通常使用他們自己的方式定義元數據。例如,使用標記interfaces,注釋,transient關鍵字等等。每個程序員按照自己的方式定義元數據,而不像Annotation這種標準的方式。
目前,許多框架將XML和Annotation兩種方式結合使用,平衡兩者之間的利弊。
下面講述java中jdk對注解(Annotation)的支持:
J2SE5.0版本在 java.lang.annotation提供了四種元注解,專門注解其他的注解:
@Documented –注解是否將包含在JavaDoc中
@Retention –什么時候使用該注解
@Target –注解用于什么地方
@Inherited – 是否允許子類繼承該注解
jdk1.6之后又根據上述的四種基本的注解聲明了java開發中常用到的注解:比如@Overrride,@SuppressWarnings,@Deprecation等,這些都是jdk1.6以后自帶的注解。
還有:
?@SafeVarargs?是JDK 7 專門為抑制“堆污染”警告提供的。
@FunctionalIterface? ?(java 8 新增的)? 函數式接口。Java8規定:如果接口中只有一個抽象方法(可以包含多個默認方法或多個static方法),該接口稱為函數式接口。
使用上述的四個注解基本量(Documented,Retention,Target,Inherited)聲明一個新的注解:
@Documented?一個簡單的Annotations標記注解,表示是否將注解信息添加在java文檔中。
@Retention?定義該注解的生命周期。
RetentionPolicy.SOURCE?– 在編譯階段丟棄。這些注解在編譯結束之后就不再有任何意義,所以它們不會寫入字節碼。@Override, @SuppressWarnings都屬于這類注解。
RetentionPolicy.CLASS?– 在類加載的時候丟棄。在字節碼文件的處理中有用。注解默認使用這種方式。
RetentionPolicy.RUNTIME– 始終不會丟棄,運行期也保留該注解,因此可以使用反射機制讀取該注解的信息。我們自定義的注解通常使用這種方式。
(三個級別)
@Target? 表示該注解用于什么地方。如果不明確指出,該注解可以放在任何地方。以下是一些可用的參數。需要說明的是:屬性的注解是兼容的,如果你想給7個屬性都添加注解,僅僅排除一個屬性,那么你需要在定義target包含所有的屬性。
ElementType.TYPE:用于描述類、接口或enum聲明
ElementType.FIELD:用于描述實例變量
ElementType.METHOD
ElementType.PARAMETER
ElementType.CONSTRUCTOR
ElementType.LOCAL_VARIABLE
ElementType.ANNOTATION_TYPE 另一個注釋
ElementType.PACKAGE 用于記錄java文件的package信息
@Inherited? 定義該注釋和子類的關系
————————————————————————————————————————————————那么,注解的內部到底是如何定義的呢?Annotations只支持基本類型、String及枚舉類型。注釋中所有的屬性被定義成方法,并允許提供默認值。(Annotations中只有成員屬性,其就是標記key,其值就是value)
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @interface Todo { public enum Priority {LOW, MEDIUM, HIGH} public enum Status {STARTED, NOT_STARTED} String author() default "Yash"; Priority priority()defaultPriority.LOW; Status status() default Status.NOT_STARTED; } 下面的例子演示了如何使用上面的注解:
@Todo(priority = Todo.Priority.MEDIUM, author ="Yashwant", status = Todo.Status.STARTED) public void incompleteMethod1() { //Some business logic is written //But it’s not complete yet } 如果注解中只有一個屬性,可以直接命名為“value”,使用時無需再標明屬性名。
@interface Author{ String value(); } @Author("Yashwant") public void someMethod() { } 但目前為止一切看起來都還不錯,我們定義了自己的注解并將其應用在業務邏輯的方法上。
但是現在我們需要寫一個用戶程序調用我們業務邏輯上使用我們自己的注解,并且根據注解做進一步處理。這里我們需要使用反射機制,如果你熟悉反射代碼,就會知道反射可以提供類名、方法和實例變量對象。所有這些對象都有getAnnotation()這個方法用來返回注解信息。我們需要把這個對象轉換為我們自定義的注釋(使用 instanceOf()檢查之后),同時也可以調用自定義注釋里面的方法。看看以下的實例代碼,使用了上面的注解:
#################################################################################
下面講一下,虛擬機層面怎么對注解元數據的保存,傳遞的。
下文所使用的java版本信息
$ java -version java version "1.8.0_20" Java(TM) SE Runtime Environment (build 1.8.0_20-b26) Java HotSpot(TM) 64-Bit Server VM (build 25.20-b23, mixed mode) java中的注解是一種繼承自接口`java.lang.annotation.Annotation`的特殊接口。public interface Annotation {... }
下面看一下具體示例。
定義一個注解:import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;/*** Created by Administrator on 2015/1/18.*/ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface TestAnnotation {int count() default 1; } 經過編譯之后,注解`TestAnnotation`的字節碼是這樣的:
Classfile /e:/workspace/intellij/SpringTest/target/classes/TestAnnotation.classLast modified 2015-1-18; size 379 bytesMD5 checksum 200dc3a75216b7a88ae17873d5dffd4fCompiled from "TestAnnotation.java" public interface TestAnnotation extends java.lang.annotation.Annotationminor version: 0major version: 52flags: ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION Constant pool:#1 = Class #14 // TestAnnotation#2 = Class #15 // java/lang/Object#3 = Class #16 // java/lang/annotation/Annotation#4 = Utf8 SourceFile#5 = Utf8 TestAnnotation.java#6 = Utf8 RuntimeVisibleAnnotations#7 = Utf8 Ljava/lang/annotation/Target;#8 = Utf8 value#9 = Utf8 Ljava/lang/annotation/ElementType;#10 = Utf8 TYPE#11 = Utf8 Ljava/lang/annotation/Retention;#12 = Utf8 Ljava/lang/annotation/RetentionPolicy;#13 = Utf8 RUNTIME#14 = Utf8 TestAnnotation#15 = Utf8 java/lang/Object#16 = Utf8 java/lang/annotation/Annotation { } SourceFile: "TestAnnotation.java" RuntimeVisibleAnnotations:0: #7(#8=[e#9.#10])1: #11(#8=e#12.#13)
從反編譯后的信息中可以看出,注解就是一個繼承自`java.lang.annotation.Annotation`的接口。
那么接口怎么能夠設置屬性呢?簡單來說就是java通過動態代理的方式為你生成了一個實現了"接口" `TestAnnotation`的實例(對于當前的實體來說,例如類、方法、屬性域等,這個代理對象是單例的),然后對該代理實例的屬性賦值,這樣就可以在程序運行時(如果將注解設置為運行時可見的話)通過反射獲取到注解的配置信息。
具體來說是怎么實現的呢?
寫一個使用該注解的類:import java.io.IOException;/** * Created by Administrator on 2015/1/18. */ @TestAnnotation(count = 0x7fffffff) public class TestMain {public static void main(String[] args) throws InterruptedException, NoSuchFieldException, IllegalAccessException, IOException {TestAnnotation annotation = TestMain.class.getAnnotation(TestAnnotation.class);System.out.println(annotation.count());System.in.read();}} 反編譯一下這段代碼:
Classfile /e:/workspace/intellij/SpringTest/target/classes/TestMain.classLast modified 2015-1-20; size 1006 bytesMD5 checksum a2d5367ea568240f078d5fb1de917550Compiled from "TestMain.java" public class TestMainminor version: 0major version: 52flags: ACC_PUBLIC, ACC_SUPER Constant pool:#1 = Methodref #10.#34 // java/lang/Object."<init>":()V#2 = Class #35 // TestMain#3 = Class #36 // TestAnnotation#4 = Methodref #37.#38 // java/lang/Class.getAnnotation:(Ljava/lang/Class;)Ljava/lang/annotation/Annotation;#5 = Fieldref #39.#40 // java/lang/System.out:Ljava/io/PrintStream;#6 = InterfaceMethodref #3.#41 // TestAnnotation.count:()I#7 = Methodref #42.#43 // java/io/PrintStream.println:(I)V#8 = Fieldref #39.#44 // java/lang/System.in:Ljava/io/InputStream;#9 = Methodref #45.#46 // java/io/InputStream.read:()I#10 = Class #47 // java/lang/Object#11 = Utf8 <init>#12 = Utf8 ()V#13 = Utf8 Code#14 = Utf8 LineNumberTable#15 = Utf8 LocalVariableTable#16 = Utf8 this#17 = Utf8 LTestMain;#18 = Utf8 main#19 = Utf8 ([Ljava/lang/String;)V#20 = Utf8 args#21 = Utf8 [Ljava/lang/String;#22 = Utf8 annotation#23 = Utf8 LTestAnnotation;#24 = Utf8 Exceptions#25 = Class #48 // java/lang/InterruptedException#26 = Class #49 // java/lang/NoSuchFieldException#27 = Class #50 // java/lang/IllegalAccessException#28 = Class #51 // java/io/IOException#29 = Utf8 SourceFile#30 = Utf8 TestMain.java#31 = Utf8 RuntimeVisibleAnnotations #32 = Utf8 count#33 = Integer 2147483647#34 = NameAndType #11:#12 // "<init>":()V#35 = Utf8 TestMain#36 = Utf8 TestAnnotation#37 = Class #52 // java/lang/Class#38 = NameAndType #53:#54 // getAnnotation:(Ljava/lang/Class;)Ljava/lang/annotation/Annotation;#39 = Class #55 // java/lang/System#40 = NameAndType #56:#57 // out:Ljava/io/PrintStream;#41 = NameAndType #32:#58 // count:()I#42 = Class #59 // java/io/PrintStream#43 = NameAndType #60:#61 // println:(I)V#44 = NameAndType #62:#63 // in:Ljava/io/InputStream;#45 = Class #64 // java/io/InputStream#46 = NameAndType #65:#58 // read:()I#47 = Utf8 java/lang/Object#48 = Utf8 java/lang/InterruptedException#49 = Utf8 java/lang/NoSuchFieldException#50 = Utf8 java/lang/IllegalAccessException#51 = Utf8 java/io/IOException#52 = Utf8 java/lang/Class#53 = Utf8 getAnnotation#54 = Utf8 (Ljava/lang/Class;)Ljava/lang/annotation/Annotation;#55 = Utf8 java/lang/System#56 = Utf8 out#57 = Utf8 Ljava/io/PrintStream;#58 = Utf8 ()I#59 = Utf8 java/io/PrintStream#60 = Utf8 println#61 = Utf8 (I)V#62 = Utf8 in#63 = Utf8 Ljava/io/InputStream;#64 = Utf8 java/io/InputStream#65 = Utf8 read {public TestMain();descriptor: ()Vflags: ACC_PUBLICCode:stack=1, locals=1, args_size=10: aload_01: invokespecial #1 // Method java/lang/Object."<init>":()V4: returnLineNumberTable:line 7: 0LocalVariableTable:Start Length Slot Name Signature0 5 0 this LTestMain;public static void main(java.lang.String[]) throws java.lang.InterruptedException, java.lang.NoSuchFieldException, java.lang.IllegalAccessException, java.io.IOException;descriptor: ([Ljava/lang/String;)Vflags: ACC_PUBLIC, ACC_STATICCode:stack=2, locals=2, args_size=10: ldc #2 // class TestMain2: ldc #3 // class TestAnnotation4: invokevirtual #4 // Method java/lang/Class.getAnnotation:(Ljava/lang/Class;)Ljava/lang/annotation/Annotation;7: checkcast #3 // class TestAnnotation10: astore_111: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;14: aload_115: invokeinterface #6, 1 // InterfaceMethod TestAnnotation.count:()I20: invokevirtual #7 // Method java/io/PrintStream.println:(I)V23: getstatic #8 // Field java/lang/System.in:Ljava/io/InputStream;26: invokevirtual #9 // Method java/io/InputStream.read:()I29: pop30: returnLineNumberTable:line 10: 0line 11: 11line 12: 23line 13: 30LocalVariableTable:Start Length Slot Name Signature0 31 0 args [Ljava/lang/String;11 20 1 annotation LTestAnnotation;Exceptions:throws java.lang.InterruptedException, java.lang.NoSuchFieldException, java.lang.IllegalAccessException, java.io.IOException } SourceFile: "TestMain.java" RuntimeVisibleAnnotations:0: #23(#32=I#33) 上述字節碼 2147483647 就是7fffffff
最后一行的代碼說明,注解`TestAnnotation`的屬性設置是在編譯時就確定了的。(對屬性的說明在[這里][1])。
然后,運行上面的程序,通過CLHSDB在eden區找到注解實例,
hsdb> scanoops 0x00000000e1b80000 0x00000000e3300000 TestAnnotation 0x00000000e1d6c360 com/sun/proxy/$Proxy1類型`com/sun/proxy/$Proxy1`是jdk動態代理生成對象時的默認類型,其中`com.sun.proxy`是默認的包名,定義于`ReflectUtil`類的`PROXY_PACKAGE`字段中。代理類名`$PROXY1`包含兩部分,其中前綴`$PROXY`是jdk種默認的代理類類名前綴(參見`java.lang.reflect.Proxy`類的javadoc),后的1是自增的結果。
下面看一下這個代理類的內容。運行java程序時添加參數`-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true`可以將轉儲出jdk動態代理類的class文件。若是項目較大或是使用了各種框架的話,慎用此參數。
Classfile /e:/workspace/intellij/SpringTest/target/classes/com/sun/proxy/$Proxy1.classLast modified 2015-1-19; size 2062 bytesMD5 checksum 7321e44402258ba9e061275e313c5c9f public final class com.sun.proxy.$Proxy1 extends java.lang.reflect.Proxy implements TestAnnotationminor version: 0major version: 49flags: ACC_PUBLIC, ACC_FINAL ...太長了,只截取一部分。從中可以看到,這個代理類實現了繼承自`java.lang.reflect.Proxy`類,又實現了“接口”TestAnnotation。
接下來查看一下代理對象的內容:
hsdb> inspect 0x00000000e1d6c360 instance of Oop for com/sun/proxy/$Proxy1 @ 0x00000000e1d6c360 @ 0x00000000e1d6c360 (size = 16) _mark: 1 _metadata._compressed_klass: InstanceKlass for com/sun/proxy/$Proxy1 h: Oop for sun/reflect/annotation/AnnotationInvocationHandler @ 0x00000000e1ce7670 Oop for sun/reflect/annotation/Annota tionInvocationHandler @ 0x00000000e1ce7670其中,0xe1ce74e0是成員變量AnnotationInvocationHandler的地址(AnnotationInvocationHandler是定義在`java.lang.reflect.Proxy`類中的),通過查看類`AnnotationInvocationHandler`的源碼可以知道注解的代理實例的值就存儲在它的成員變量`memberValues`中,然后繼續向下挖就好了:
hsdb> inspect 0x00000000e1ce7670 instance of Oop for sun/reflect/annotation/AnnotationInvocationHandler @ 0x00000000e1ce7670 @ 0x00000000e1ce7670 (size =24) _mark: 1 _metadata._compressed_klass: InstanceKlass for sun/reflect/annotation/AnnotationInvocationHandler type: Oop for java/lang/Class @ 0x00000000e1ccc5f8 Oop for java/lang/Class @ 0x00000000e1ccc5f8 memberValues: Oop for java/util/LinkedHashMap @ 0x00000000e1ce7548 Oop for java/util/LinkedHashMap @ 0x00000000e1ce7548 memberMethods: null null hsdb> inspect 0x00000000e1ce7548 instance of Oop for java/util/LinkedHashMap @ 0x00000000e1ce7548 @ 0x00000000e1ce7548 (size = 56) _mark: 1 _metadata._compressed_klass: InstanceKlass for java/util/LinkedHashMap keySet: null null values: null null table: ObjArray @ 0x00000000e1ce75b8 Oop for [Ljava/util/HashMap$Node; @ 0x00000000e1ce75b8 entrySet: null null size: 1 modCount: 1 threshold: 1 loadFactor: 0.75 head: Oop for java/util/LinkedHashMap$Entry @ 0x00000000e1ce75d0 Oop for java/util/LinkedHashMap$Entry @ 0x00000000e1ce7 5d0 tail: Oop for java/util/LinkedHashMap$Entry @ 0x00000000e1ce75d0 Oop for java/util/LinkedHashMap$Entry @ 0x00000000e1ce75d0 accessOrder: false hsdb> inspect 0x00000000e1ce75d0 instance of Oop for java/util/LinkedHashMap$Entry @ 0x00000000e1ce75d0 @ 0x00000000e1ce75d0 (size = 40) _mark: 1 _metadata._compressed_klass: InstanceKlass for java/util/LinkedHashMap$Entry hash: 94852264 key: "count" @ 0x00000000e1bd7c90 Oop for java/lang/String @ 0x00000000e1bd7c90 value: Oop for java/lang/Integer @ 0x00000000e1ce7630 Oop for java/lang/Integer @ 0x00000000e1ce7630 next: null null before: null null after: null null hsdb> inspect 0x00000000e1ce7630 instance of Oop for java/lang/Integer @ 0x00000000e1ce7630 @ 0x00000000e1ce7630 (size = 16) _mark: 1 _metadata._compressed_klass: InstanceKlass for java/lang/Integer value: 2147483647最后可以看到,key=“count”, value=Integer(2147483647 = 0x7fffffff),正是在TestMain中設置的值.
總結:
jvm對注解的支持,就是底層創建一個動態代理類,然后代理類中創建各個AnnotationInvocationHandler對象;每個AnnotationInvocationHandler對應一個我們聲明的Annotation接口類,且AnnotationInvocationHandler收集對應注解接口類中的屬性鍵值對。我們通過反射method.getAnnotation(**Annotation.class)或者field.getAnnotation(**Annotation.class),底層就是調用代理類,獲取AnnotationInvocationHandler中的memberValues。
總結
以上是生活随笔為你收集整理的对java注解的深入理解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 80×86汇编语言_站长窝
- 下一篇: win10快捷方式存在问题怎么办?