1. 程式人生 > >Java註解的實現原理

Java註解的實現原理

註解是Java裡特殊的定義形式,我們用@interface定義一個註解,然後定義其屬性,之後此註解可以被標識在屬性,方法,類,註解等目標上。我們再使用註解是,通過目標物件的getDeclaredAnnotataion(..)等方法獲取註解例項物件。但是這個例項物件是什麼物件,是我們定義的註解物件嗎?如果是,那麼我們定義的註解是如何生成例項物件的?如果不是,那麼我們獲取到的是什麼物件?這個就是本篇部落格要講的問題

註解很多時候被叫做Annotation,Annotation是一個介面,但是我們定義的註解和這個介面看起來沒有什麼直接的聯絡,為什麼叫Annotation ? 在泛型或者反射獲取一個Class物件時,我們確定獲取的Class是一個註解型別,因此可以定義Class<\? extends Annotation>,此字面上理解是註解型別繼承自Annotation,是Annotation的一個拓展,我們定義的註解是如何和Annotation扯上關係的?這也是本篇部落格講的問題。

先看自定義的一個註解:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE,ElementType.FIELD})
public @interface After {


    String value();

    String argNames() default "";

}

我們定義了一個註解After,定義了兩個方法,其中一個方法有預設實現返回空字串。
@interface,一個@符號+一個介面。可以將註解近視的看做是一個interface,裡面定義了方法。當方法沒有預設值時,則需要在使用註解時顯式的賦值。介面的方法其實也是可以有預設實現的,比如:

public interface Inter{
    default boolean exists() {
        return false;
    }
}

當一個類實現了此介面時,有預設實現的方法可以不重寫。
使用一個註解近視的看做是定義一個介面的實現類,重寫那些沒有預設值的方法,相當於定義註解的屬性值。

那麼我們通過反射獲取到的註解物件是我們定義的註解@After嗎?如果是,它是以什麼樣的形式生成的?如果不是,那又是什麼?

其實我們通過反射獲取到的物件是一個jdk Proxy動態代理生成的物件,整個物件繼承自Proxy,實現了After介面。因此定義的@After實際就看做是一個介面,這個介面繼承了Annotation介面。java在解析一個註解時,將註解視為一個interface,繼承自Annotation,用jdk proxy為註解介面動態的生成代理物件。所以說所有的@interface 均 extends Annotation,我們想要獲取獲取註解的值時,可以像類一樣使用反射獲取其方法,然後呼叫方法獲取定義的值。

Annotation接口裡有一個Class

package com.bob.test.concrete.annotation;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

import org.junit.Before;
import org.junit.Test;
import org.springframework.util.ReflectionUtils;

/**
 * @since 2017年8月4日 上午9:09:27
 */
public class AnnotationTest {

    private Method targetMethod;
    private After after; // 明確知道是哪個註解
    private Annotation ann; // 確定是註解,但是不確定型別

    @After(value = "lanboal")
    public void testAnn() {

    }

    @Before
    public void init() {
        targetMethod = ReflectionUtils.findMethod(AnnotationTest.class, "testAnn");
        after = targetMethod.getDeclaredAnnotation(After.class);
        ann = targetMethod.getDeclaredAnnotation(After.class);
    }

    @Test
    public void testEquals() throws NoSuchMethodException, SecurityException {
        System.out.println("after:" + after.toString() + ",\t ann:" + ann.toString());
        // After和Annotation這兩個變數引用的是同一個物件
        System.out.println(after == ann ? "after == ann" : "after != ann"); 
        System.out.println("After.annotationType:" + after.annotationType() + ",\t ann.annotationType:" + ann.annotationType());
        System.out.println("After.Class:" + after.getClass() + ",\t ann.Class:" + ann.getClass());
    }

    @Test
    public void testGetClass() {
        // 父類是Proxy
        System.out.println("after.superClass:" + after.getClass().getSuperclass() + ", ann.superClass:" + ann.getClass().getSuperclass()); 
        // 當前Proxy$Num物件實現了@After註解
        System.out.println( 
                "after.interfaces:" + after.getClass().getInterfaces()[0].getName() + ", ann.interfaces:" + ann.getClass().getInterfaces()[0].getName());
        // 體現@After(看做是一個介面)繼承Annotation介面
        System.out.println(after.getClass().getInterfaces()[0].getInterfaces()[0].getName()); 
    }

    @Test
    public void testGetMethod() throws Exception {
        // 代理物件Proxy$Num內的value()方法
        Method after0 = after.getClass().getMethod("value"); 
        // @Afte的value()方法
        Method after1 = after.annotationType().getMethod("value"); 
        // 代理物件Proxy$Num內的value()方法
        Method ann0 = ann.getClass().getMethod("value"); 
        // @Afte的value()方法
        Method ann1 = ann.annotationType().getMethod("value"); 
        System.out.println("[after0]:" + after0.toString() + ", [after1]:" + after1.toString());
        System.out.println("[ann0]:" + ann0.toString() + ", [ann1]:" + ann1.toString());
        System.out.println(after0 == ann0 ? "after0 == ann0" : "after0 != ann0");

        // 執行Proxy$Num.value()方法時會呼叫其代理的@After物件的value()方法,所以得到的結果和@After.value()的結果相同
        System.out.println("after0.invoke:" + after0.invoke(after) + ", after1.invoke:" + after1.invoke(after));
    }

    @Test
    public void testGetAnnType(){
        Class<? extends Annotation> aClass = ann.annotationType();
        System.out.println(aClass.getName());
    }

}