java註解是怎麼實現的

下文所使用的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`的特殊接口。參見下面的[JLS][2]和JDK文檔。

An annotation type declaration specifies a new annotation type, 
a special kind of interface type. To distinguish an annotation 
type declaration from a normal interface declaration, the keyword 
interface is preceded by an at-sign (@).

...

The direct superinterface of every annotation type is java.lang.annotation.Annotation.

/**
 * The common interface extended by all annotation types.  Note that an
 * interface that manually extends this one does <i>not</i> define
 * an annotation type.  Also note that this interface does not itself
 * define an annotation type.
 *
 * More information about annotation types can be found in section 9.6 of
 * <cite>The Java&trade; Language Specification</cite>.
 *
 * The {@link java.lang.reflect.AnnotatedElement} interface discusses
 * compatibility concerns when evolving an annotation type from being
 * non-repeatable to being repeatable.
 *
 * @author  Josh Bloch
 * @since   1.5
 */
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.class
  Last modified 2015-1-18; size 379 bytes
  MD5 checksum 200dc3a75216b7a88ae17873d5dffd4f
  Compiled from "TestAnnotation.java"
public interface TestAnnotation extends java.lang.annotation.Annotation
  minor version: 0
  major version: 52
  flags: 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.class
  Last modified 2015-1-20; size 1006 bytes
  MD5 checksum a2d5367ea568240f078d5fb1de917550
  Compiled from "TestMain.java"
public class TestMain
  minor version: 0
  major version: 52
  flags: 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: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 7: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       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;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: ldc           #2                  // class TestMain
         2: ldc           #3                  // class TestAnnotation
         4: invokevirtual #4                  // Method java/lang/Class.getAnnotation:(Ljava/lang/Class;)Ljava/lang/annotation/Annotation;
         7: checkcast     #3                  // class TestAnnotation
        10: astore_1
        11: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
        14: aload_1
        15: invokeinterface #6,  1            // InterfaceMethod TestAnnotation.count:()I
        20: invokevirtual #7                  // Method java/io/PrintStream.println:(I)V
        23: getstatic     #8                  // Field java/lang/System.in:Ljava/io/InputStream;
        26: invokevirtual #9                  // Method java/io/InputStream.read:()I
        29: pop
        30: return
      LineNumberTable:
        line 10: 0
        line 11: 11
        line 12: 23
        line 13: 30
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      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)

最後一行的代碼說明,註解`TestAnnotation`的屬性設置是在編譯時就確定了的。(對屬性的說明在[這裏][1])。

然後,運行上面的程序,通過CLHSDB在eden區找到註解實例,

hsdb> scanoops 0x00000000e1b80000 0x00000000e3300000 TestAnnotation
0x00000000e1d6c360 com/sun/proxy/$Proxy1

類型`com/sun/proxy/Proxy1jdkcom.sun.proxyReflectUtilPROXYPACKAGE 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.class
  Last modified 2015-1-19; size 2062 bytes
  MD5 checksum 7321e44402258ba9e061275e313c5c9f
public final class com.sun.proxy.$Proxy1 extends java.lang.reflect.Proxy implements TestAnnotation
  minor version: 0
  major version: 49
  flags: 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是成員變量h的地址(h是定義在`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中設置的值.

嗯,就這樣吧。


[1]: Chapter 4. The class File Format
[2]: Chapter 9. Interfaces
發佈了65 篇原創文章 · 獲贊 67 · 訪問量 16萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章