首先寫一個簡單的自定義註解小程序
//先自定義一個運行時註解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface HelloAnnotation {
String say() default "Hi";
}
然後在Main函數中解析註解
@HelloAnnotation(say = "Do it!")
public class TestMain {
public static void main(String[] args) {
HelloAnnotation annotation = TestMain.class.getAnnotation(HelloAnnotation.class);//獲取TestMain類上的註解對象
System.out.println(annotation.say());//調用註解對象的say方法,並打印到控制檯
}
}
輸出結果:Do it!
下面將圍繞上面的代碼來研究Java註解(Annotation)的實現原理
1. 註解對象具體是什麼?
首先,我們先在main函數第一行斷點,看看HelloAnnotation具體是什麼類的對象
可以看到HelloAnnotation註解的實例是jvm生成的動態代理類的對象。
這個運行時生成的動態代理對象是可以導出到文件的,方法有兩種
- 在代碼中加入System.setProperty(“sun.misc.ProxyGenerator.saveGeneratedFiles”, “true”);
- 在運行時加入jvm 參數 -Dsun.misc.ProxyGenerator.saveGeneratedFiles=true
我們使用第一種如下:
@HelloAnnotation(say = "Do it!")
public class TestMain {
public static void main(String[] args) {
/* 設置此係統屬性,讓JVM生成的Proxy類寫入文件.保存路徑爲:com/sun/proxy(如果不存在請手工創建) */
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
HelloAnnotation annotation = TestMain.class.getAnnotation(HelloAnnotation.class);//獲取TestMain類上的註解對象
System.out.println(annotation.say());//調用註解對象的say方法,並打印到控制檯
}
}
然後運行程序。
可以看到,已經導出了運行時生成的代理類,而且每個分別實現了一個接口。
HelloAnnotation的動態代理類是$Proxy3.class,Intellij自帶了反編譯工具,直接雙擊打開,得到如下的Java代碼:
public final class $Proxy3 extends Proxy implements HelloAnnotation {
private static Method m3;
private static Method m1;
private static Method m0;
private static Method m4;
private static Method m2;
public $Proxy3(InvocationHandler var1) throws {
super(var1);
}
public final String say() throws {
try {
return (String)super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final boolean equals(Object var1) throws {
try {
return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final int hashCode() throws {
try {
return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final Class annotationType() throws {
try {
return (Class)super.h.invoke(this, m4, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m3 = Class.forName("HelloAnnotation").getMethod("say", new Class[0]);
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
m4 = Class.forName("HelloAnnotation").getMethod("annotationType", new Class[0]);
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
從第一行我們可以看到,我們自定義的註解HelloAnnotation是一個接口,而$Proxy1這個Java生成的動態代理類就是它的實現類。
我們接着看一下HelloAnnotation的字節碼
$ javap -verbose HelloAnnotation
public interface HelloAnnotation extends java.lang.annotation.Annotation
SourceFile: "HelloAnnotation.java"
RuntimeVisibleAnnotations:
0: #11(#12=[e#13.#14])
1: #15(#12=e#16.#17)
minor version: 0
major version: 51
flags: ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
Constant pool:
#1 = Class #18 // HelloAnnotation
#2 = Class #19 // java/lang/Object
#3 = Class #20 // java/lang/annotation/Annotation
#4 = Utf8 say
#5 = Utf8 ()Ljava/lang/String;
#6 = Utf8 AnnotationDefault
#7 = Utf8 Hi
#8 = Utf8 SourceFile
#9 = Utf8 HelloAnnotation.java
#10 = Utf8 RuntimeVisibleAnnotations
#11 = Utf8 Ljava/lang/annotation/Target;
#12 = Utf8 value
#13 = Utf8 Ljava/lang/annotation/ElementType;
#14 = Utf8 TYPE
#15 = Utf8 Ljava/lang/annotation/Retention;
#16 = Utf8 Ljava/lang/annotation/RetentionPolicy;
#17 = Utf8 RUNTIME
#18 = Utf8 HelloAnnotation
#19 = Utf8 java/lang/Object
#20 = Utf8 java/lang/annotation/Annotation
{
public abstract java.lang.String say();
flags: ACC_PUBLIC, ACC_ABSTRACT
AnnotationDefault:
default_value: s#7}
HelloAnnotation就是繼承了Annotation的接口。再看第10行,flag字段中,我們可以看到,有個ACC_ANNOTATION標記,說明是一個註解,所以註解本質是一個繼承了Annotation的特殊接口。
而Annotation接口聲明瞭以下方法。
public interface Annotation {
boolean equals(Object var1);
int hashCode();
String toString();
Class<? extends Annotation> annotationType();
}
這些方法,已經被$Proxy3實現了。(這就是動態代理的機制)
小結
現在我們知道了HelloAnnotation註解(接口)是一個繼承了Annotation接口的特殊接口,而我們通過反射獲取註解時,返回的是Java運行時生成的動態代理對象$Proxy3,該類就是HelloAnnotation註解(接口)的具體實現類。
二、動態代理類$Proxy3如何處理annotation.say()方法的調用
無論是否瞭解動態代理,這裏只需要明確一點,動態代理方法的調用最終會傳遞給綁定的InvocationHandler實例的invoke方法處理。我們可以看看源碼
$Proxy3.java
public final class $Proxy1 extends Proxy implements HelloAnnotation {
.....
public final String say() throws {
try {
return (String)super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
....
}
從上面不難看出,say方法最終會執行(String)super.h.invoke(this, m3, (Object[])null);,而這其中的h對象類型就是InvocationHandler接口的某個實現類
斷點調試,看看InvocationHandler具體實現類是哪個。
可以看到h對象是AnnotationInvocationHandler的實例,讓我們來看看該實現類的invoke方法。
class AnnotationInvocationHandler implements InvocationHandler, Serializable {
private static final long serialVersionUID = 6182022883658399397L;
private final Class<? extends Annotation> type;
private final Map<String, Object> memberValues;
private transient volatile Method[] memberMethods = null;
AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {
Class[] var3 = var1.getInterfaces();
if(var1.isAnnotation() && var3.length == 1 && var3[0] == Annotation.class) {
this.type = var1;
this.memberValues = var2;
} else {
throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
}
}
public Object invoke(Object var1, Method var2, Object[] var3) {
String var4 = var2.getName();
Class[] var5 = var2.getParameterTypes();
if(var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
return this.equalsImpl(var3[0]);
} else if(var5.length != 0) {
throw new AssertionError("Too many parameters for an annotation method");
} else {
byte var7 = -1;
switch(var4.hashCode()) {
case -1776922004:
if(var4.equals("toString")) {
var7 = 0;
}
break;
case 147696667:
if(var4.equals("hashCode")) {
var7 = 1;
}
break;
case 1444986633:
if(var4.equals("annotationType")) {
var7 = 2;
}
}
switch(var7) {
case 0:
return this.toStringImpl();
case 1:
return Integer.valueOf(this.hashCodeImpl());
case 2:
return this.type;
default:
Object var6 = this.memberValues.get(var4);
if(var6 == null) {
throw new IncompleteAnnotationException(this.type, var4);
} else if(var6 instanceof ExceptionProxy) {
throw ((ExceptionProxy)var6).generateException();
} else {
if(var6.getClass().isArray() && Array.getLength(var6) != 0) {
var6 = this.cloneArray(var6);
}
return var6;
}
}
}
}
.......
}
我們直接從invoke方法第一行開始單步調試,看看invoke方法是如何處理我們annotation.say()方法的調用的。
可以看到,say方法的返回值是從一個Map中獲取到的。這個map以key(註解方法名)—value(註解方法對應的值)存儲TestMain類上的註解
那memberValues這個Map對象是怎麼生成的,繼續調試
通過方法調用棧找到memberValues的本源。
‘do,it’是在parseMemberValue()方法中獲取的,我們繼續跟進parseMemberValue()方法
public static Object parseMemberValue(Class<?> var0, ByteBuffer var1, ConstantPool var2, Class<?> var3) {
Object var4 = null;
byte var5 = var1.get();
switch(var5) {
case 64:
var4 = parseAnnotation(var1, var2, var3, true);
break;
case 91:
return parseArray(var0, var1, var2, var3);
case 99:
var4 = parseClassValue(var1, var2, var3);
break;
case 101:
return parseEnumValue(var0, var1, var2, var3);
default:
var4 = parseConst(var5, var1, var2); //此處會調用parseConst方法,繼續跟進到parseConst方法
}
if(!(var4 instanceof ExceptionProxy) && !var0.isInstance(var4)) {
var4 = new AnnotationTypeMismatchExceptionProxy(var4.getClass() + "[" + var4 + "]");
}
return var4;
}
private static Object parseConst(int var0, ByteBuffer var1, ConstantPool var2) {
int var3 = var1.getShort() & '\uffff';
switch(var0) {
case 66:
return Byte.valueOf((byte)var2.getIntAt(var3));
case 67:
return Character.valueOf((char)var2.getIntAt(var3));
case 68:
return Double.valueOf(var2.getDoubleAt(var3));
case 70:
return Float.valueOf(var2.getFloatAt(var3));
case 73:
return Integer.valueOf(var2.getIntAt(var3));
case 74:
return Long.valueOf(var2.getLongAt(var3));
case 83:
return Short.valueOf((short)var2.getIntAt(var3));
case 90:
return Boolean.valueOf(var2.getIntAt(var3) != 0);
case 115:
return var2.getUTF8At(var3); //memberValues是通過常量池獲取到,這裏的var3就是常量池中的序號。
default:
throw new AnnotationFormatError("Invalid member-value tag in annotation: " + var0);
}
}
調用完parseConst方法,然後返回到parseMemberValue()方法
可以看到獲取的就是我們定義在TestMain類上註解的say的值——“Do it!”
這裏可以通過javap -verbose TestMain查看TestMain字節碼中的常量池。
$ javap -verbose TestMain
public class com.kevin.java.annotation.runtimeAnnotation.TestMain
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool://常量池
#1 = Methodref #11.#30 // java/lang/Object."<init>":()V
#2 = String #31 // sun.misc.ProxyGenerator.saveGeneratedFiles
#3 = String #32 // true
#4 = Methodref #33.#34 // java/lang/System.setProperty:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
#5 = Class #35 // com/kevin/java/annotation/runtimeAnnotation/TestMain
#6 = Class #36 // com/kevin/java/annotation/runtimeAnnotation/HelloAnnotation
#7 = Methodref #37.#38 // java/lang/Class.getAnnotation:(Ljava/lang/Class;)Ljava/lang/annotation/Annotation;
#8 = Fieldref #33.#39 // java/lang/System.out:Ljava/io/PrintStream;
#9 = InterfaceMethodref #6.#40 // com/kevin/java/annotation/runtimeAnnotation/HelloAnnotation.say:()Ljava/lang/String;
#10 = Methodref #41.#42 // java/io/PrintStream.println:(Ljava/lang/String;)V
#11 = Class #43 // java/lang/Object
#12 = Utf8 <init>
#13 = Utf8 ()V
#14 = Utf8 Code
#15 = Utf8 LineNumberTable
#16 = Utf8 LocalVariableTable
#17 = Utf8 this
#18 = Utf8 Lcom/kevin/java/annotation/runtimeAnnotation/TestMain;
#19 = Utf8 main
#20 = Utf8 ([Ljava/lang/String;)V
#21 = Utf8 args
#22 = Utf8 [Ljava/lang/String;
#23 = Utf8 annotation
#24 = Utf8 Lcom/kevin/java/annotation/runtimeAnnotation/HelloAnnotation;
#25 = Utf8 SourceFile
#26 = Utf8 TestMain.java
#27 = Utf8 RuntimeVisibleAnnotations
#28 = Utf8 say
#29 = Utf8 Do it! //這裏就是Do it
#30 = NameAndType #12:#13 // "<init>":()V
#31 = Utf8 sun.misc.ProxyGenerator.saveGeneratedFiles
#32 = Utf8 true
#33 = Class #44 // java/lang/System
#34 = NameAndType #45:#46 // setProperty:(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
#35 = Utf8 com/kevin/java/annotation/runtimeAnnotation/TestMain
#36 = Utf8 com/kevin/java/annotation/runtimeAnnotation/HelloAnnotation
#37 = Class #47 // java/lang/Class
#38 = NameAndType #48:#49 // getAnnotation:(Ljava/lang/Class;)Ljava/lang/annotation/Annotation;
#39 = NameAndType #50:#51 // out:Ljava/io/PrintStream;
#40 = NameAndType #28:#52 // say:()Ljava/lang/String;
#41 = Class #53 // java/io/PrintStream
#42 = NameAndType #54:#55 // println:(Ljava/lang/String;)V
#43 = Utf8 java/lang/Object
#44 = Utf8 java/lang/System
#45 = Utf8 setProperty
#46 = Utf8 (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
#47 = Utf8 java/lang/Class
#48 = Utf8 getAnnotation
#49 = Utf8 (Ljava/lang/Class;)Ljava/lang/annotation/Annotation;
#50 = Utf8 out
#51 = Utf8 Ljava/io/PrintStream;
#52 = Utf8 ()Ljava/lang/String;
#53 = Utf8 java/io/PrintStream
#54 = Utf8 println
#55 = Utf8 (Ljava/lang/String;)V
到此爲止,say方法就完成了。
總結
註解本質是一個繼承了Annotation的特殊接口,其具體實現類是Java運行時生成的動態代理類。通過代理對象調用自定義註解(接口)的方法,會最終調用AnnotationInvocationHandler的invoke方法。該方法會從memberValues這個Map中索引出對應的值。而memberValues的來源是Java常量池。