JAva注釈はどのように実現されていますか
16290 ワード
以下で使用するjavaバージョン情報
JAvaの注記は、インタフェース`フェース`java.lang.annotation.Annotation`から継承される特殊なインタフェースです.以下の[JLS][2]およびJDKドキュメントを参照してください.
具体例を見てみましょう.注記を定義します.
コンパイル後、注記`TestAnnotation`のバイトコードは次のようになります.
逆コンパイルされた情報から、注記は`java.lang.annotation.Annotation`から継承されたインタフェースであることがわかります.
では、インタフェースはどのように属性を設定することができますか?簡単に言えばjavaは動的エージェントによって実装された「インタフェース」`TestAnnotation`のインスタンスを生成し(現在のエンティティではクラス、メソッド、プロパティドメインなど、このエージェントオブジェクトは単一の例である)、その後、そのエージェントインスタンスのプロパティに値を割り当てることで、プログラムの実行時に(注釈を実行時に表示するように設定すると)注記の構成情報は、反射によって取得されます.
具体的にはどうやって実現したのでしょうか.注釈を使用するクラスを書きます.
このコードを逆コンパイルします.
最後の行のコード説明では、注記`TestAnnotation`のプロパティ設定はコンパイル時に決定されます.(属性の説明は[ここ][1]).
次に、上記のプログラムを実行し、CLHSDBを介してeden領域に注釈インスタンスを見つけ、
タイプ`com/sun/proxy/Proxy 1'は、jdkダイナミックエージェントがオブジェクトを生成するときのデフォルトのタイプであり、「com.sun.proxy」はデフォルトのパッケージ名であり、「ReflectUtil」クラスの「PROXYPACKAGE」フィールドに定義されます.エージェントクラス名‘P r o x y 1’は、j d k動的エージェントがオブジェクトを生成する際のデフォルトタイプであり、‘c o m.s u n.p r o x y’はデフォルトのパケット名であり、‘R e f l e c t U t i l’クラスの‘P R O X Y P A C G E’フィールドに定義される.エージェントクラス名‘PROXY 1’は2つの部分を含み、接頭辞`$PROXY’はjdk種のデフォルトのエージェントクラス名接頭辞であり(`java.lang.reflect.Proxy`クラスのjavadocを参照)、後の1は自己増加の結果である.
このエージェントクラスの内容を見てみましょう.Javaプログラムの実行時にパラメータ`-DSun.misc.ProxyGenerator.saveGeneratedFiles=true`を追加すると、jdkダイナミックエージェントクラスのclassファイルがダンプされます.プロジェクトが大きい場合や、さまざまなフレームワークが使用されている場合は、このパラメータを慎重に使用します.
長すぎて、一部だけ切り取ります.このエージェントクラスは`java.lang.reflect.Proxy`クラスから継承され、「インタフェース」TestAnnotationも実装されていることがわかります.
次に、プロキシオブジェクトの内容を確認します.
ここで、0 xe 1 ce 74 e 0はメンバー変数hのアドレス(hは`java.lang.reflect.Proxy`クラスに定義されている)であり、クラス`AnnotationInvocationHandler`のソースコードを見ることで注釈のエージェントインスタンスの値がそのメンバー変数`memberValues`に格納されていることを知ることができ、次に掘り下げ続けるとよい.
最後に、key="count"、value=Integer(2147483647=0 x 7 fffffffff)がTestMainで設定値であることがわかる.
うん、そうしよう.
[1]: Chapter 4. The class File Format
[2]: Chapter 9. Interfaces
$ 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 not 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
* The Java™ Language Specification.
*
* 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."":()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
#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 // "":()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."":()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/Proxy 1'は、jdkダイナミックエージェントがオブジェクトを生成するときのデフォルトのタイプであり、「com.sun.proxy」はデフォルトのパッケージ名であり、「ReflectUtil」クラスの「PROXYPACKAGE」フィールドに定義されます.エージェントクラス名‘P r o x y 1’は、j d k動的エージェントがオブジェクトを生成する際のデフォルトタイプであり、‘c o m.s u n.p r o x y’はデフォルトのパケット名であり、‘R e f l e c t U t i l’クラスの‘P R O X Y P A C G E’フィールドに定義される.エージェントクラス名‘PROXY 1’は2つの部分を含み、接頭辞`$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
ここで、0 xe 1 ce 74 e 0はメンバー変数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=0 x 7 fffffffff)がTestMainで設定値であることがわかる.
うん、そうしよう.
[1]: Chapter 4. The class File Format
[2]: Chapter 9. Interfaces