カスタム注釈の実装方法.md

7743 ワード

最も簡単な言葉で注釈を記述すると、メタデータ、すなわちデータを記述するデータである.したがって,注釈はソースコードのメタデータであるといえる.JavaではAnnotationと呼ばれ、Annotationはクラス、メソッド、パラメータ、変数、コンストラクタ、およびパッケージ宣言に適用される特殊な修飾子です.Javaが持参した注記は@Override,@Supperwarning,@Deprescated
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
}

この列挙は3つの値,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 getAnnotation(Class annotationClass);
        Annotation[] getAnnotations();
        default  T[] getAnnotationsByType(Class 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 getDeclaredAnnotation(Class 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[] getDeclaredAnnotationsByType(Class 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 annotationClass):プログラム要素に指定したタイプの注釈が含まれているかどうかを判断し、存在する場合はtrueを返します.そうでない場合はfalse.4.Annotation[]getDeclaredAnnotations()を返します.この要素に直接存在するすべての注釈を返します.このインタフェースの他のメソッドとは異なり、継承された注記は無視されます.この方法の呼び出し者は、返される配列を任意に変更することができる.これは、他の呼び出し元が返す配列に影響を与えません.