如何實現自定義註解.md

如果用最簡單的話描述註解,那就是元數據,即一種描述數據的數據。所以,可以說註解就是源代碼的元數據。
在Java中叫Annotation,Annotation是一種應用於類、方法、參數、變量、構造器及包聲明中的特殊修飾符。
Java自帶的註解有@Override,@Supperwarning,@Deprecated

1.註解的作用

1.生成文檔,比如@see @param @return
2.在編譯時進行格式檢查。如@override 放在方法前,如果你這個方法並不是覆蓋了超類方法,則編譯時就能檢查出
3.追蹤代碼依賴性,實現替代配置文件,比如Spring的各種註解等

2.如何實現一個自定義註解

我們先寫一個最簡單的註解:

@Retention(RetentionPolicy.RUNTIME)
public @interface FunctionDesc {
}

上面這個註解沒有任何的屬性定義,我們看下定義一個註解所需的元素。首先使用關鍵字“@interface” 表明這類是註解類;然後這類上加上註解“@Retention”,這是定義註解所必須的。

註解元素

@Retention是註解的註解,稱爲元註解。我們看一下@Retention的內部:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    /**
     * Returns the retention policy.
     * @return the retention policy
     */
    RetentionPolicy value();
}

我們看到Retention本身也被@Retention註解,還有@Documented和@Target(ElementType.ANNOTATION_TYPE),ANNOTATION_TYPE表明這個註解應應用在註解上。
Retention有一個屬性value,類型爲枚舉的RetentionPolicy,它沒有默認值,所以不寫這個值就會報錯,我們繼續看一下RetentionPolicy:

public enum RetentionPolicy {
    /**
     * Annotations are to be discarded by the compiler.
     */
    SOURCE,

    /**
     * Annotations are to be recorded in the class file by the compiler
     * but need not be retained by the VM at run time.  This is the default
     * behavior.
     */
    CLASS,

    /**
     * Annotations are to be recorded in the class file by the compiler and
     * retained by the VM at run time, so they may be read reflectively.
     *
     * @see java.lang.reflect.AnnotatedElement
     */
    RUNTIME
}

這個枚舉定義了三個值,SOURCE,CLASS,RUNTIME。分別定義我們寫的註解保持到哪個生命階段。把上面的英文註釋翻譯過來:

  • RetentionPolicy.SOURCE 註解的信息會被編譯器拋棄,不會留在class文件中
  • RetentionPolicy.CLASS 註解將由編譯器記錄在類文件(.class)中 但不需要在VM在運行時保留。 這是默認值行爲
  • RetentionPolicy.RUNTIME 註解的信息被保留在.class文件中當程序編譯時,會被虛擬機保留在運行時

如此,我們實現並解釋了最簡單的註解,那麼註解怎麼起作用呢,我們借測試來說明下:

public class AnnotationTest {

    @FunctionDesc
    public void work(){}

    public static void main(String[] args) {
        try {
            Method workMethod = AnnotationTest.class.getMethod("work",null);
            if(workMethod.isAnnotationPresent(FunctionDesc.class)){
                System.out.println(workMethod.getAnnotation(FunctionDesc.class));
            }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }
}

運行上面的代碼,會輸出下面這句:
@com.wy.core.annotation.FunctionDesc()
這表明我們的註解起作用了。細心的你會發現,註解的真正起作用是藉助於“反射”完成的,這個我們待會深講。

3.註解中的屬性

上面我們定義的@FunctionDesc是沒有任何功能的。實際上註解的功能實現是通過定義屬性完成的。我們給FunctionDesc加上屬性:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface FunctionDesc {
    String desc();
    int paramCount() default 0;
    String returnDesc() default "";
}

public class AnnotationTest {

    @FunctionDesc(desc = "幹活")
    public void work(){}
    public static void main(String[] args) {
        try {
            Method workMethod = AnnotationTest.class.getMethod("work",null);
            if(workMethod.isAnnotationPresent(FunctionDesc.class)){
                FunctionDesc fd = workMethod.getAnnotation(FunctionDesc.class);
                System.out.println(fd);
                System.out.println("desc="+fd.desc());
                System.out.println("paramCount="+fd.paramCount());
            }
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }
}

運行代碼後輸出:
@com.wy.core.annotation.FunctionDesc(paramCount=0, returnDesc=, desc=幹活)
desc=幹活
paramCount=0

我們添加了@Target({ElementType.METHOD}),表明這個註解適用在方法上。內部定義了3個屬性,desc是用作方法描述 ,paramCount表明參數個數,returnDesc是返回值描述。
paramCount和returnDesc有默認值,註解時可以不填值,但是desc必須寫值。

上面這個測試的例子,我們從AnnotationTest.class 反射拿到了work方法的Method定義,從Method獲取了它上面的註解信息。
這就是註解功能處理實現的大致思路。

4.反射得到註解的相關方法

我們知道反射的相關類有Class,Method,Field,Constructor等,它們都實現了AnnotatedElement接口。AnnotatedElement是註解處理器類庫的核心元素。
我們看下AnnotatedElement的定義

public interface AnnotatedElement {
    default boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) {
        return getAnnotation(annotationClass) != null;
    }

    <T extends Annotation> T getAnnotation(Class<T> annotationClass);
    Annotation[] getAnnotations();
    default <T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass) {
         /*
          * Definition of associated: directly or indirectly present OR
          * neither directly nor indirectly present AND the element is
          * a Class, the annotation type is inheritable, and the
          * annotation type is associated with the superclass of the
          * element.
          */
         T[] result = getDeclaredAnnotationsByType(annotationClass);

         if (result.length == 0 && // Neither directly nor indirectly present
             this instanceof Class && // the element is a class
             AnnotationType.getInstance(annotationClass).isInherited()) { // Inheritable
             Class<?> superClass = ((Class<?>) this).getSuperclass();
             if (superClass != null) {
                 // Determine if the annotation is associated with the
                 // superclass
                 result = superClass.getAnnotationsByType(annotationClass);
             }
         }

         return result;
     }
    default <T extends Annotation> T getDeclaredAnnotation(Class<T> annotationClass) {
         Objects.requireNonNull(annotationClass);
         // Loop over all directly-present annotations looking for a matching one
         for (Annotation annotation : getDeclaredAnnotations()) {
             if (annotationClass.equals(annotation.annotationType())) {
                 // More robust to do a dynamic cast at runtime instead
                 // of compile-time only.
                 return annotationClass.cast(annotation);
             }
         }
         return null;
     }
    default <T extends Annotation> T[] getDeclaredAnnotationsByType(Class<T> annotationClass) {
        Objects.requireNonNull(annotationClass);
        return AnnotationSupport.
            getDirectlyAndIndirectlyPresent(Arrays.stream(getDeclaredAnnotations()).
                                            collect(Collectors.toMap(Annotation::annotationType,
                                                                     Function.identity(),
                                                                     ((first,second) -> first),
                                                                     LinkedHashMap::new)),
                                            annotationClass);
    }
    Annotation[] getDeclaredAnnotations();
}

所以我們通過反射得到Class,Method,Field,Constructor之後,就可以通過以上方法獲取註解信息了,最常用的以下幾個方法
1.getAnnotation(Class annotationClass): 返回改程序元素上存在的、指定類型的註解,如果該類型註解不存在,則返回null。
2.getAnnotations():返回該程序元素上存在的所有註解。
3.isAnnotationPresent(Class<?extends Annotation> annotationClass):判斷該程序元素上是否包含指定類型的註解,存在則返回true,否則返回false.
4.Annotation[] getDeclaredAnnotations():返回直接存在於此元素上的所有註解。與此接口中的其他方法不同,該方法將忽略繼承的註解。該方法的調用者可以隨意修改返回的數組;這不會對其他調用者返回的數組產生任何影響。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章