用Java快三年了,註解算是一個經常使用的類型,特別是在一些框架裏面會大量使用註解作組件標識、配置或者策略。可是一直沒有深刻去探究JDK中的註解究竟是什麼,底層是怎麼實現了?因而參考了一些資料,作了一次稍微詳細的分析。html
參考JavaSE-8裏面的JLS-9.6對註解的描述以下:java
註解的聲明以下:數組
{InterfaceModifier} @ interface Identifier AnnotationTypeBody 接口修飾符 @ interface 註解標識符 註解類型的內容
其中:緩存
java.lang.annotation.Annotation
。既然全部註解類型的父接口都是java.lang.annotation.Annotation
,那麼咱們能夠看一下Annotation接口的文檔:oracle
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.ide
Since: 1.5this
JavaSE-8中的文檔對Annotation的描述和JLS-9.6中差很少,不過最後指明瞭可重複註解的兼容性考慮的問題,可重複註解在JDK1.8中由元註解@Repeatable實現。下面基於JDK8的最後一個版本java version 1.8.0_181
探究一下註解在JDK中的底層實現。代理
咱們先定義一個十分簡單的Counter註解以下:code
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])
若是熟悉字節碼,從直觀上能夠獲得下面的信息:
@Counter
對應的接口接口除了繼承了java.lang.annotation.Annotation中的抽象方法,自身定義了一個抽象方法public abstract int count();
。既然註解最後轉化爲一個接口,註解中定義的註解成員屬性會轉化爲抽象方法,那麼最後這些註解成員屬性怎麼進行賦值的呢?答案就是:爲註解對應的接口生成一個實現該接口的動態代理類。直接點說就是:Java經過動態代理的方式生成了一個實現了"註解對應接口"的實例,該代理類實例實現了"註解成員屬性對應的方法",這個步驟相似於"註解成員屬性"的賦值過程,這樣子就能夠在程序運行的時候經過反射獲取到註解的成員屬性(這裏註解必須是運行時可見的,也就是使用了@Retention(RetentionPolicy.RUNTIME),另外須要理解JDK原生動態代理和反射相關內容)。
上面一些已經指出了,註解的最底層實現就是一個JDK的動態代理類,而這個動態代理類的生成過程咱們徹底能夠經過Debug跟蹤,這裏列舉一下筆者跟蹤整個過程的流水帳:
Class<?>#getAnnotation(Class<A> annotationClass)
,經過類型獲取註解實例。Class<?>#annotationData()
,獲取註解的數據。Class<?>#createAnnotationData(int classRedefinedCount)
,構建註解的數據。AnnotationParser#parseAnnotations(byte[] var0, ConstantPool var1, Class<?> var2)
,這裏已是sun包下的類,沒法看到源碼,這個方法用於解析註解,這一步使用到字節碼中常量池的索引解析,常量解析完畢會生成一個成員屬性鍵值對做爲下一個環節的入參,常量池的解析能夠看AnnotationParser#parseMemberValue
方法。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類的源碼:
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
。-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)