Java 註解原理
下面來看看Java中註解是如何實現的
建立註解類Inter:
建立測試類Test:
在程式第二句設定斷點,可以看到:
可以看到,註解的例項是一個動態代理類的物件.
要想檢視這個動態代理類,可以在程式碼中加
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
新增系統代理,將其匯出為class檔案
可以看到如下兩個檔案:
反編譯$Proxy1.class,如下:
可以看到,動態代理類是我們定義的註解實現類,反編譯Inner.class,如下:
可以看到,註解介面繼承了java.lang.annotation.Annotation, 通過檢視原始碼,該類原始碼如下:
可以看到, 該類下的方法都被$Proxy1動態代理類實現了.
到此處,我們已經知道Inner註解(介面)是一個繼承了Annotation介面的特殊介面,而我們通過反射獲取註解時,返回的是Java執行時生成的動態代理物件$Proxy1,該類就是Inner註解(介面)的具體實現類。
那麼, 代理類是如何處理方法的呼叫的呢?
我們知道, 動態代理方法的呼叫最終會傳遞給繫結的InvocationHandler例項的invoke方法處理。我們可以看看$Proxy1的原始碼
其中語句呼叫了父類的成員變數,其父類為Proxy, 檢視該成員變數,如下:
可以看到, h物件型別就是InvocationHandler介面的某個實現類
我們在Proxy類的構造方法處設定斷點:
通過斷點可以檢視h具體是哪個物件:
可以看到, 該動態代理類為AnnotationInvocationHandler物件, 檢視該類的invoke方法如下:
其中的memberValues變數是以方法名為key,以變數為value的, 如下:
那麼,這個memberValues變數是從哪來的呢?
可以看到,其是在建構函式中進行設定的.
反編譯我們的Test類,看到:
所以中間有一個類,負責建立代理物件AnnotationInvocationHandler, 其將變數從常量池中取出並建立map, 進而建立代理物件, 這個類就是 AnnotationParser, 在此不細說了, 感興趣的可以自行斷點除錯檢視.
總結
註解本質是一個繼承了Annotation的特殊介面,其具體實現類是Java執行時生成的動態代理類。通過代理物件呼叫自定義註解(介面)的方法,會最終呼叫AnnotationInvocationHandler的invoke方法。該方法會從memberValues這個Map中索引出對應的值。而memberValues的來源是Java常量池。