淺析java註解底層原理

註解是一種附加信息/標識信息,若是沒有對應的處理器,那沒有任何做用.java

註解概述

元註解

@Retention

註解留存的時間節點:ide

  • RetentionPolicy.SOURCE:當前註解編譯期可見,不會寫入 class 文件,常見的註解爲@Override, @SuppressWarnings
  • RetentionPolicy.CLASS:會寫入 class 文件,在類加載後丟棄,能夠定義對字節碼的操做
  • RetentionPolicy.RUNTIME:永久保存,能夠反射獲取

@Target

註解做用的範圍:this

  • ElementType.TYPE:容許被修飾的註解做用在類、接口和枚舉上
  • ElementType.FIELD:容許做用在屬性字段上
  • ElementType.METHOD:容許做用在方法上
  • ElementType.PARAMETER:容許做用在方法參數上
  • ElementType.CONSTRUCTOR:容許做用在構造器上
  • ElementType.LOCAL_VARIABLE:容許做用在本地局部變量上
  • ElementType.ANNOTATION_TYPE:容許做用在註解上
  • ElementType.PACKAGE:容許做用在包上

@Inherited

是否容許子類繼承該註解,注意,這不是說註解容許繼承,而是若是你用註解標註了一個父類,是否容許這個父類的子類也自動擁有這個註解.spa

@Documented

註解是否應當被包含在 JavaDoc 文檔中代理

編譯器註解(java內置)

  • @Deprecated 表示棄用,編譯這樣的類/方法等編譯器會給警告.它直接能夠標註的東西不少,能夠點進去看下@Target. 注意配合javadoc註釋,告訴別人新的在哪裏.
/** @deprecated This class is full of bugs. Use MyNewComponent instead. */
複製代碼
  • @Override 表示父類進行重寫的方法
  • @SuppressWarnings 用來抑制編譯器生成警告信息。

註解底層原理

奇怪的變量定義方式

先看一個接口public interface Annotation,在它的doc描述中有一句The common interface extended by all annotation types,也就是全部註解都會繼承這個接口.那麼註解其實也是接口,它在運行時會有代理類,這也就解釋了,爲何定義註解使用的變量看起來會有點奇怪,竟然要定義個方法.code

@Target({ElementType.METHOD,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Hello {
    String helloKey() default "helloValue";
}
複製代碼

由於註解實際會有代理類,因此定義的屬性確實是方法,只不過方法名字和屬性名相同,屬性值由方法return. 這裏可能有人會問,爲何不直接定義變量,由於java用接口實現註解,接口沒法定義實例變量.cdn

代理

其實後面就是代理的事情了,默認是JDK的動態代理.
能夠使用System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");來在當前工程中保存JDK生成的動態代理類.
看一下代理類對象

public final class $Proxy1 extends Proxy implements Hello {
    // equals
    private static Method m1;
    // toString
    private static Method m2;
    // annotationType
    private static Method m4;
    // hashCode
    private static Method m0;
    // 註解定義的屬性對應的方法
    private static Method m3;
		
    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.learn.java.annotation.Hello").getMethod("annotationType");
            m3 = Class.forName("com.learn.java.annotation.Hello").getMethod("helloKey");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }

    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 String helloKey() throws {
        try {
            return (String)super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }
}
複製代碼

能夠看出,Proxy類的實際邏輯都是交給InvocationHandler h的invoke方法處理的,
invoke(this,方法Method對象,方法參數列表),這是JDK代理的通用邏輯,代理後的實際邏輯在對應的handler. 對於註解,它的處理handler是AnnotationInvocationHandler.
同時,咱們能夠看到代理類Proxy對四個方法是有特殊處理的,這四個方法的實際邏輯也在對應的handler.若是沒有這樣操做的話,其實代理類會使用Object的實現或者找不到.blog

  • private static Method m1;// equals
  • private static Method m2;// toString
  • private static Method m4;// annotationType
  • private static Method m0;// hashCode

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;
    // var1是Proxy$1代理類的this,var2是傳進來的方法對象,var3是方法參數
    // 咱們只關注var2就好,別的沒用到
    public Object invoke(Object var1, Method var2, Object[] var3) {
        // 方法名
        String var4 = var2.getName();
        // 方法參數
        Class[] var5 = var2.getParameterTypes();
        // equals特殊處理
        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;
            // 這裏判斷方法名的hashcode
            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:
                    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;
                    }
            }
        }
    }
}		



複製代碼

能夠看到,若是是4種特殊方法,那麼var7會被設成對應的值,直接調用invoker中複寫的方法. 若是是equals在上邊就直接判斷了.
若是不是,說明方法是註解自定義的方法,其實也就是註解的參數.
會從memberValues中取得,它是一個Map<String, Object>,會存儲當前註解的屬性名(方法名)爲Key,以及對應的值.

舉個例子

@Target({ElementType.METHOD,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Hello {
    String helloKey() default "helloValue";
}
複製代碼

那麼memberValues存放的值是

固然,這是因爲虛擬機讀取到註解的Retention是Runtime.若是不是運行時的註解,那麼不會有本身的AnnotationInvocationvandler.
注意虛擬機會給每個運行時註解生成一個AnnotationInvocationvandler類的實例,type屬性是註解的類型.

一個註解,多個地方使用也就是有多個實例,也會有多個AnnotationInvocationvandler類的實例

相關文章
相關標籤/搜索