<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          一文讀懂注解的底層原理

          共 16272字,需瀏覽 33分鐘

           ·

          2021-03-26 20:51

          點擊上方老周聊架構(gòu)關(guān)注我



          一、前言

          上一篇我們說了注解、元注解以及自定義注解,詳情請戳:一文讀懂Annotation

          注解的聲明如下:

          {InterfaceModifier} @interface Identifier AnnotationTypeBody
          接口修飾符 @interface 注解標(biāo)識符 注解類型的內(nèi)容

          其中:

          • 注解類型聲明中的標(biāo)識符指定了注解類型的名稱。

          • 如果注解類型與它的任何封閉類或接口具有相同的簡單名稱,則編譯時會出現(xiàn)錯誤。

          • 每個注解類型的直接父接口都是 java.lang.annotation.Annotation。


          二、注解的底層實現(xiàn)

          我們來自定義一個流控 RateLimit 注解,如下:

          @Retention(RetentionPolicy.RUNTIME)@Documented@Target(ElementType.TYPE)public @interface RateLimit {    int value() default 0;}

          我們用反射獲取 RateLimit 注解,然后斷點調(diào)試觀察下反射究竟是怎么樣的魔力。

          @RateLimit(value = 666)public class RateLimitMain {    public static void main(String[] args) {        RateLimit rateLimit = RateLimitMain.class.getAnnotation(RateLimit.class);        System.out.println(rateLimit.value());    }}

          斷點調(diào)試 @RateLimit 過程中,發(fā)現(xiàn)它是一個代理類。

          為了驗證這一點我們使用 JDK 的反編譯命令查看 @RateLimit 的字節(jié)碼:

          javap -c -v RateLimit.class

          @RateLimit 反編譯后的字節(jié)碼如下:

          Classfile /Users/Riemann/Code/spring-boot-demo/target/classes/com/riemann/springbootdemo/annotation/RateLimit.class  Last modified 2021-3-23; size 495 bytes  MD5 checksum 1ce59bd6cc4c7297e8d3f032320b04d3  Compiled from "RateLimit.java"public interface com.riemann.springbootdemo.annotation.RateLimit extends java.lang.annotation.Annotation  minor version: 0  major version: 52  flags: ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATIONConstant pool:   #1 = Class              #18            // com/riemann/springbootdemo/annotation/RateLimit   #2 = Class              #19            // java/lang/Object   #3 = Class              #20            // java/lang/annotation/Annotation   #4 = Utf8               value   #5 = Utf8               ()I   #6 = Utf8               AnnotationDefault   #7 = Integer            0   #8 = Utf8               SourceFile   #9 = Utf8               RateLimit.java  #10 = Utf8               RuntimeVisibleAnnotations  #11 = Utf8               Ljava/lang/annotation/Retention;  #12 = Utf8               Ljava/lang/annotation/RetentionPolicy;  #13 = Utf8               RUNTIME  #14 = Utf8               Ljava/lang/annotation/Documented;  #15 = Utf8               Ljava/lang/annotation/Target;  #16 = Utf8               Ljava/lang/annotation/ElementType;  #17 = Utf8               TYPE  #18 = Utf8               com/riemann/springbootdemo/annotation/RateLimit  #19 = Utf8               java/lang/Object  #20 = Utf8               java/lang/annotation/Annotation{  public abstract int value();    descriptor: ()I    flags: ACC_PUBLIC, ACC_ABSTRACT    AnnotationDefault:      default_value: I#7}SourceFile: "RateLimit.java"RuntimeVisibleAnnotations:  0: #11(#4=e#12.#13)  1: #14()  2: #15(#4=[e#16.#17])

          從上述字節(jié)碼可以看出:

          • 注解是一個接口,它繼承自 java.lang.annotation.Annotation 父接口。

          • @RateLimit 自身定義了一個抽象方法 public abstract int value(); 。


          既然注解最后轉(zhuǎn)化為一個接口,注解中定義的注解成員屬性會轉(zhuǎn)化為抽象方法,那么最后這些注解成員屬性怎么進(jìn)行賦值的呢?

          答案就是:為注解對應(yīng)的接口生成一個實現(xiàn)該接口的動態(tài)代理類。

          直接點就是:Java 通過動態(tài)代理的方式生成了一個實現(xiàn)了"注解對應(yīng)接口"的實例,該代理類實例實現(xiàn)了"注解成員屬性對應(yīng)的方法",這個步驟類似于"注解成員屬性"的賦值過程,這樣子就可以在程序運行的時候通過反射獲取到注解的成員屬性(這里注解必須是運行時可見的,也就是使用了@Retention(RetentionPolicy.RUNTIME),另外動態(tài)代理相關(guān)知識這里就不展開講,留在下一篇講)。

          到這里就解決了上一篇的疑問了,注解的值是如何獲取到的,現(xiàn)在恍然大悟。

          本著科學(xué)嚴(yán)謹(jǐn)?shù)膽B(tài)度,我們還是來瞅一瞅那個代理類是如何獲取到注解上的值的。

          三、揭秘注解背后的代理類

          注解的最底層實現(xiàn)就是一個 JDK 的動態(tài)代理類,而這個動態(tài)代理類的生成過程在 Debug 面前一覽無余。

          JDK 中是通過 AnnotatedElement(package java.lang.reflect)接口實現(xiàn)對注解的解析,我們的 Class 類實現(xiàn)了 AnnotatedElement 接口。

          public final class Class<T> implements java.io.Serializable,                              GenericDeclaration,                              Type,                              AnnotatedElement {  ...}

          AnnotatedElement 代碼:

          AnnotatedElement 的注釋:

          Represents an annotated element of the program currently running in this VM. This interface allows annotations to be read reflectively.

          翻譯過來就是:

          AnnotatedElement 代表了 jvm 中一個正在運行的被注解元素,這個接口允許通過反射的方式讀取注解。

          可以看下 Class 類中對于 AnnotatedElement 接口都是如何實現(xiàn)的:

          我們這就跟著 Debug 來看下 getAnnotation 方法

          根據(jù)注解的 class 實例從類的注解緩存數(shù)據(jù)中獲取匹配的注解類型

          RateLimit 是注解類型,RateLimit.getClass() 獲取到的就是 Class 實例

          1、Class<?>#getAnnotation(Class<A> annotationClass),通過類型獲取注解實例。

          public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {    Objects.requireNonNull(annotationClass);
          return (A) annotationData().annotations.get(annotationClass);}

          2、代碼中 annotationData().annotations 是一個 Map(key 為注解的 Class 實例,value 為注解類型)。

          // annotation data that might get invalidated when JVM TI RedefineClasses() is calledprivate static class AnnotationData {    final Map<Class<? extends Annotation>, Annotation> annotations;    final Map<Class<? extends Annotation>, Annotation> declaredAnnotations;
          // Value of classRedefinedCount when we created this AnnotationData instance final int redefinedCount;
          AnnotationData(Map<Class<? extends Annotation>, Annotation> annotations, Map<Class<? extends Annotation>, Annotation> declaredAnnotations, int redefinedCount) { this.annotations = annotations; this.declaredAnnotations = declaredAnnotations; this.redefinedCount = redefinedCount; }}

          3、Class<?>#annotationData(),獲取注解的數(shù)據(jù)。

          private AnnotationData annotationData() {    while (true) { // retry loop        AnnotationData annotationData = this.annotationData;        int classRedefinedCount = this.classRedefinedCount;        if (annotationData != null &&            annotationData.redefinedCount == classRedefinedCount) {            return annotationData;        }        // null or stale annotationData -> optimistically create new instance        AnnotationData newAnnotationData = createAnnotationData(classRedefinedCount);        // try to install it        if (Atomic.casAnnotationData(this, annotationData, newAnnotationData)) {            // successfully installed new AnnotationData            return newAnnotationData;        }    }}

          核心的邏輯是:當(dāng) this.annotationData 為空,解析類中的 annotationData 并寫入 this.annotationData,最后都會返回 this.annotationData。

          4、其中 Atomic.casAnnotationData(this, annotationData, newAnnotationData) 的作用便是將解析到的 annotationData 寫入 this.annotationData 。

          static <T> boolean casAnnotationData(Class<?> clazz,                                     AnnotationData oldData,                                     AnnotationData newData) {    return unsafe.compareAndSwapObject(clazz, annotationDataOffset, oldData, newData);}


          其中 unsafe.compareAndSwapObject 是一個 native 方法

          5、createAnnotationData(classRedefinedCount) 的作用是解析類中用到的 annotationData

          private AnnotationData createAnnotationData(int classRedefinedCount) {    Map<Class<? extends Annotation>, Annotation> declaredAnnotations =        AnnotationParser.parseAnnotations(getRawAnnotations(), getConstantPool(), this);    Class<?> superClass = getSuperclass();    Map<Class<? extends Annotation>, Annotation> annotations = null;    if (superClass != null) {        Map<Class<? extends Annotation>, Annotation> superAnnotations =            superClass.annotationData().annotations;        for (Map.Entry<Class<? extends Annotation>, Annotation> e : superAnnotations.entrySet()) {            Class<? extends Annotation> annotationClass = e.getKey();            if (AnnotationType.getInstance(annotationClass).isInherited()) {                if (annotations == null) { // lazy construction                    annotations = new LinkedHashMap<>((Math.max(                            declaredAnnotations.size(),                            Math.min(12, declaredAnnotations.size() + superAnnotations.size())                        ) * 4 + 2) / 3                    );                }                annotations.put(annotationClass, e.getValue());            }        }    }    if (annotations == null) {        // no inherited annotations -> share the Map with declaredAnnotations        annotations = declaredAnnotations;    } else {        // at least one inherited annotation -> declared may override inherited        annotations.putAll(declaredAnnotations);    }    return new AnnotationData(annotations, declaredAnnotations, classRedefinedCount);}

          整個的處理邏輯是:

          • 獲取類本身的 declaredAnnotations

          • 獲取父類的 annotations

          • 將 declaredAnnotations+annotations 整合,返回


          6、AnnotationParser#parseAnnotations(byte[] var0, ConstantPool var1, Class<?> var2)

          這里已經(jīng)是 sun 包下的類,這個方法用于解析注解,這一步使用到字節(jié)碼中常量池的索引解析,常量解析完畢會生成一個成員屬性鍵值對作為下一個環(huán)節(jié)的入?yún)ⅲA砍氐慕馕隹梢钥?/span>AnnotationParser#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);    }
          if (!(var4 instanceof ExceptionProxy) && !var0.isInstance(var4)) { var4 = new AnnotationTypeMismatchExceptionProxy(var4.getClass() + "[" + var4 + "]"); }
          return var4;}

          7、AnnotationParser#annotationForMap(final Class<? extends Annotation> var0, final Map<String, Object> var1)

          同樣是sun.reflect.annotation.AnnotationParser中的方法,用于生成注解的動態(tài)代理類。

          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 動態(tài)代理的這里的代碼應(yīng)該看起來很簡單,就是生成一個標(biāo)準(zhǔn)的 JDK 動態(tài)代理,而 InvocationHandler 的實例是 AnnotationInvocationHandler,可以看它的成員變量、構(gòu)造方法和實現(xiàn) InvocationHandler 接口的 invoke 方法:

          class AnnotationInvocationHandler implements InvocationHandler, Serializable {    private static final long serialVersionUID = 6182022883658399397L;    // 保存了當(dāng)前注解的類型    private final Class<? extends Annotation> type;    // 保存了注解的成員屬性的名稱和值的映射,注解成員屬性的名稱實際上就對應(yīng)著接口中抽象方法的名稱    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) { // 獲取當(dāng)前執(zhí)行的方法名稱 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 { // 這一步就是注解成員屬性返回值獲取的實際邏輯 // 需要判斷是否是數(shù)組,如果是數(shù)組需要克隆一個數(shù)組 // 不是數(shù)組直接返回 if (var6.getClass().isArray() && Array.getLength(var6) != 0) { var6 = this.cloneArray(var6); }
          return var6; } } } } ...}

          既然知道了注解底層使用了 JDK 原生的 Proxy,那么我們可以直接輸出代理類到指定目錄去分析代理類的源碼,有兩種方式可以輸出 Proxy 類的源碼:

          • 通過 Java 系統(tǒng)屬性設(shè)置:
            System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");。

          • 通過 -D 參數(shù)指定,參數(shù)是:
            -Dsun.misc.ProxyGenerator.saveGeneratedFiles=true。


          這里使用方式 1,修改一下上面用到的 RateLimitMain 方法:

          @RateLimit(value = 666)public class RateLimitMain {    public static void main(String[] args) {        System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");        RateLimit rateLimit = RateLimitMain.class.getAnnotation(RateLimit.class);        System.out.println(rateLimit.value());    }}

          執(zhí)行完畢之后,項目中多了一個目錄:

          其中 $Proxy0 是 @Retention 注解對應(yīng)的動態(tài)代理類,而 $Proxy1 才是我們的 @RateLimit 對應(yīng)的動態(tài)代理類,當(dāng)然如果有更多的注解,那么有可能生成 $ProxyN接著我們直接看 $Proxy1 的源碼:

          public final class $Proxy1 extends Proxy implements RateLimit {    private static Method m1;    private static Method m2;    private static Method m4;    private static Method m0;    private static Method m3;
          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 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); } }
          public final int value() throws { try { return (Integer)super.h.invoke(this, m3, (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"); m4 = Class.forName("com.riemann.springbootdemo.annotation.RateLimit").getMethod("annotationType"); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); m3 = Class.forName("com.riemann.springbootdemo.annotation.RateLimit").getMethod("value"); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } }}

          顯然,$Proxy1 實現(xiàn)了 RateLimit 接口,它在代碼的最后部分使用了靜態(tài)代碼塊實例化了成員方法的 Method 實例,在前面的代碼對這些 Method 進(jìn)行了緩存,在調(diào)用成員方法的時候都是直接委托到 InvocationHandler(AnnotationInvocationHandler) 實例完成調(diào)用。我們在分析 AnnotationInvocationHandler 的時候看到,它只用到了 Method 的名稱從 Map 從匹配出成員方法的結(jié)果,因此調(diào)用過程并不是反射調(diào)用,反而是直接的調(diào)用,效率類似于通過 Key 從 Map 實例中獲取 Value 一樣,是十分高效的。

          好了,這一篇顯然解決了上一篇“注解是如何通過反射獲取值的呢?”的疑問,下一篇來說一下“AOP 切面織入底層是如何實現(xiàn)的呢?”的這個疑問,敬請期待~



          歡迎大家關(guān)注我的公眾號【老周聊架構(gòu)】,Java后端主流技術(shù)棧的原理、源碼分析、架構(gòu)以及各種互聯(lián)網(wǎng)高并發(fā)、高性能、高可用的解決方案。

          喜歡的話,點贊、再看、分享三連。

          點個在看你最好看



          瀏覽 79
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产精品久久久久久视频 | 国产精品九九99久久精品 | 五月天丁香婷婷亚洲无码 | 天天撸一撸在线免费观看 | 99热在线观看8 |