前提
用Java快三年了,注解算是一個常用的類型,特別是在一些框架里面會大量使用注解做組件標識、配置或者策略。但是一直沒有深入去探究JDK中的注解到底是什么,底層是怎么實現了?於是參考了一些資料,做了一次稍微詳細的分析。
JDK的注解描述
參考JavaSE-8里面的JLS-9.6對注解的描述如下:
注解的聲明如下:
{InterfaceModifier} @ interface Identifier AnnotationTypeBody
接口修飾符 @ interface 注解標識符 注解類型的內容
其中:
- 注解類型聲明中的標識符指定了注解類型的名稱。
- 如果注解類型與它的任何封閉類或接口具有相同的簡單名稱,則編譯時會出現錯誤。
- 每個注解類型的直接父接口都是
java.lang.annotation.Annotation
。
既然所有注解類型的父接口都是java.lang.annotation.Annotation
,那么我們可以看一下Annotation接口的文檔:
public interface 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 AnnotatedElement interface discusses compatibility concerns when evolving an annotation type from being non-repeatable to being repeatable.
Since: 1.5
JavaSE-8中的文檔對Annotation的描述和JLS-9.6中差不多,不過最后指明了可重復注解的兼容性考慮的問題,可重復注解在JDK1.8中由元注解@Repeatable實現。下面基於JDK8的最后一個版本java version 1.8.0_181
探究一下注解在JDK中的底層實現。
注解實現探究
我們先定義一個十分簡單的Counter注解如下:
package club.throwable.annotation;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.TYPE)
public @interface Counter {
int count() default 0;
}
我們先從直接使用@Counter
注解,從直觀上觀察@Counter
實例的類型:
@Counter(count = 1)
public class Main {
public static void main(String[] args) throws Exception{
Counter counter = Main.class.getAnnotation(Counter.class);
System.out.println(counter.count());
}
}
@Counter
實例從Debug過程中觀察發現是JDK的一個代理類(並且InvocationHandler的實例是sun.reflect.annotation.AnnotationInvocationHandler,它是一個修飾符為default的sun包內可用的類),為了驗證這一點我們使用JDK的反編譯命令查看@Counter
的字節碼:
javap -c -v D:\Projects\rxjava-seed\target\classes\club\throwable\annotation\Counter.class
@Counter
反編譯后的字節碼如下:
Classfile /D:/Projects/rxjava-seed/target/classes/club/throwable/annotation/Counter.class
Last modified 2018-10-6; size 487 bytes
MD5 checksum 83cee23f426e5b51a096281068d8b555
Compiled from "Counter.java"
public interface club.throwable.annotation.Counter extends java.lang.annotation.Annotation
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
Constant pool:
#1 = Class #19 // club/throwable/annotation/Counter
#2 = Class #20 // java/lang/Object
#3 = Class #21 // java/lang/annotation/Annotation
#4 = Utf8 count
#5 = Utf8 ()I
#6 = Utf8 AnnotationDefault
#7 = Integer 0
#8 = Utf8 SourceFile
#9 = Utf8 Counter.java
#10 = Utf8 RuntimeVisibleAnnotations
#11 = Utf8 Ljava/lang/annotation/Retention;
#12 = Utf8 value
#13 = Utf8 Ljava/lang/annotation/RetentionPolicy;
#14 = Utf8 RUNTIME
#15 = Utf8 Ljava/lang/annotation/Documented;
#16 = Utf8 Ljava/lang/annotation/Target;
#17 = Utf8 Ljava/lang/annotation/ElementType;
#18 = Utf8 TYPE
#19 = Utf8 club/throwable/annotation/Counter
#20 = Utf8 java/lang/Object
#21 = Utf8 java/lang/annotation/Annotation
{
public abstract int count();
descriptor: ()I
flags: ACC_PUBLIC, ACC_ABSTRACT
AnnotationDefault:
default_value: I#7}
SourceFile: "Counter.java"
RuntimeVisibleAnnotations:
0: #11(#12=e#13.#14)
1: #15()
2: #16(#12=[e#17.#18])
如果熟悉字節碼,從直觀上可以得到下面的信息:
- 注解是一個接口,它繼承自java.lang.annotation.Annotation父接口。
@Counter
對應的接口接口除了繼承了java.lang.annotation.Annotation中的抽象方法,自身定義了一個抽象方法public abstract int count();
。
既然注解最后轉化為一個接口,注解中定義的注解成員屬性會轉化為抽象方法,那么最后這些注解成員屬性怎么進行賦值的呢?答案就是:為注解對應的接口生成一個實現該接口的動態代理類。直接點說就是:Java通過動態代理的方式生成了一個實現了"注解對應接口"的實例,該代理類實例實現了"注解成員屬性對應的方法",這個步驟類似於"注解成員屬性"的賦值過程,這樣子就可以在程序運行的時候通過反射獲取到注解的成員屬性(這里注解必須是運行時可見的,也就是使用了@Retention(RetentionPolicy.RUNTIME),另外需要理解JDK原生動態代理和反射相關內容)。
注解對應的動態代理類實例
上面一些已經指出了,注解的最底層實現就是一個JDK的動態代理類,而這個動態代理類的生成過程我們完全可以通過Debug跟蹤,這里列舉一下筆者跟蹤整個過程的流水賬:
- 1、
Class<?>#getAnnotation(Class<A> annotationClass)
,通過類型獲取注解實例。 - 2、
Class<?>#annotationData()
,獲取注解的數據。 - 3、
Class<?>#createAnnotationData(int classRedefinedCount)
,構建注解的數據。 - 4、
AnnotationParser#parseAnnotations(byte[] var0, ConstantPool var1, Class<?> var2)
,這里已經是sun包下的類,無法看到源碼,這個方法用於解析注解,這一步使用到字節碼中常量池的索引解析,常量解析完畢會生成一個成員屬性鍵值對作為下一個環節的入參,常量池的解析可以看AnnotationParser#parseMemberValue
方法。 - 5、
AnnotationParser#annotationForMap(final Class<? extends Annotation> var0, final Map<String, Object> var1)
,同樣是sun.reflect.annotation.AnnotationParser
中的方法,用於生成注解的動態代理類。
注意第5步,貼出它的源碼:
public static Annotation annotationForMap(final Class<? extends Annotation> var0, final Map<String, Object> var1) {
return (Annotation)AccessController.doPrivileged(new PrivilegedAction<Annotation>() {
public Annotation run() {
return (Annotation)Proxy.newProxyInstance(var0.getClassLoader(), new Class[]{var0}, new AnnotationInvocationHandler(var0, var1));
}
});
}
熟悉JDK動態代理的這里的代碼應該看起來很簡單,就是生成一個標准的JDK動態代理,而InvocationHandler的實例是AnnotationInvocationHandler,可以看它的成員變量、構造方法和實現InvocationHandler接口的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 this.hashCodeImpl();
case 2:
return this.type;
default:
//利用方法名稱從memberValues獲取成員屬性的賦值
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;
}
}
}
}
//忽略其他方法
這里需要重點注意一下的是:AnnotationInvocationHandler的成員變量Map<String, Object> memberValues
存放着注解的成員屬性的名稱和值的映射,注解成員屬性的名稱實際上就對應着接口中抽象方法的名稱,例如上面我們定義的@Counter
注解生成代理類后,它的AnnotationInvocationHandler實例中的memberValues屬性存放着鍵值對count=1
。
既然知道了注解底層使用了JDK原生的Proxy,那么我們可以直接輸出代理類到指定目錄去分析代理類的源碼,有兩種方式可以輸出Proxy類的源碼:
- 1、通過Java系統屬性設置
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
。 - 2、通過-D參數指定,其實跟1差不多,參數是:
-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true
。
這里使用方式1,修改一下上面用到的main方法:
public static void main(String[] args) throws Exception {
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
Counter counter = Main.class.getAnnotation(Counter.class);
System.out.println(counter.count());
}
執行完畢之后,項目中多了一個目錄:
其中$Proxy0是@Retention注解對應的動態代理類,而$Proxy1才是我們的@Counter對應的動態代理類,當然如果有更多的注解,那么有可能生成$ProxyN。接着我們直接看$Proxy1的源碼:
public final class $Proxy1 extends Proxy implements Counter {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m4;
private static Method m0;
public $Proxy1(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
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);
}
}
public final int count() throws {
try {
return (Integer)super.h.invoke(this, m3, (Object[])null);
} 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 int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("club.throwable.annotation.Counter").getMethod("count");
m4 = Class.forName("club.throwable.annotation.Counter").getMethod("annotationType");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
顯然,$Proxy1實現了Counter接口,它在代碼的最后部分使用了靜態代碼塊實例化了成員方法的Method實例,在前面的代碼對這些Method進行了緩存,在調用成員方法的時候都是直接委托到InvocationHandler(AnnotationInvocationHandler)實例完成調用。我們在分析AnnotationInvocationHandler的時候看到,它只用到了Method的名稱從Map從匹配出成員方法的結果,因此調用過程並不是反射調用,反而是直接的調用,效率類似於通過Key從Map實例中獲取Value一樣,是十分高效的。
小結
既然知道了注解的底層原理,我們可以編寫一個"注解接口"和InvocationHandler實現來簡單模擬整個過程。先定義一個接口:
public interface CounterAnnotation extends Annotation {
int count();
}
InvocationHandler的簡單實現:
public class CounterAnnotationInvocationHandler implements InvocationHandler {
private final Map<String, Object> memberValues;
private final Class<? extends Annotation> clazz;
public CounterAnnotationInvocationHandler(Map<String, Object> memberValues, Class<? extends Annotation> clazz) {
this.memberValues = memberValues;
this.clazz = clazz;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
Object value;
switch (methodName) {
case "toString":
value = super.toString();
break;
case "hashCode":
value = super.hashCode();
break;
case "equals":
value = super.equals(args[0]);
break;
case "annotationType":
value = clazz;
break;
default:
value = memberValues.get(methodName);
}
return value;
}
}
編寫一個main方法:
public class CounterAnnotationMain {
public static void main(String[] args) throws Exception{
//這里模擬了注解成員屬性從常量池解析的過程
Map<String,Object> values = new HashMap<>(8);
values.put("count", 1);
//生成代理類
CounterAnnotation proxy = (CounterAnnotation)Proxy.newProxyInstance(CounterAnnotationMain.class.getClassLoader(),
new Class[]{CounterAnnotation.class},
new CounterAnnotationInvocationHandler(values, CounterAnnotation.class));
System.out.println(proxy.count());
}
}
//運行后控制台輸出:1
(本文完 c-1-d e-20181006)
技術公眾號(《Throwable文摘》),不定期推送筆者原創技術文章(絕不抄襲或者轉載):
娛樂公眾號(《天天沙雕》),甄選奇趣沙雕圖文和視頻不定期推送,緩解生活工作壓力: