[Java] 32. Reflection機能を使う方法(Annotation編)


Study / Java    作成日付 : 2019/09/24 00:19:25   修正日付 : 2021/04/22 13:49:39

こんにちは。明月です。


この投稿はJavaのReflection機能を使う方法(Annotation編)に関する説明です。


以前の投稿でJavaのReflection機能をClassとMethod、Variableを分けて説明しました。

link - [Java] 29. Reflection機能を使う方法(Class編)

link - [Java] 30. Reflection機能を使う方法(Method編)

link - [Java] 31. Reflection機能を使う方法(Variable編)


今まではReflectionがクラスを割り当てするか内部関数、変数の値を取得することで使いました。

JavaではAnnotationは機能が何もないです。AnnotationはJavaでメタデータの役だけです。つまり、Javaのコードの解析記述や説明に関数データです。

でも、AnnotationはJavaのReflectionと一緒で使えば単純なメタデータの機能だけではないです。

import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

// アノテーション生成(クラス用)
// RUNTIME設定してないと実行する時にgetDeclaredAnnotationsで探索ができない。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface TestAnnotation {
  // 基本default値
  public String value() default "";
}
// アノテーション設定
@TestAnnotation("Hello world")
class Example {
  // 実行関数
  public static void main(String[] args) {
    // Exampleでアノテーション
    Annotation[] annos = Example.class.getAnnotations();
    // すべてアノテーション出力
    for (Annotation anno : annos) {
      // コンソール出力
      System.out.println(anno.toString());
      // TestAnnotationアノテーションなら
      if (anno.annotationType() == TestAnnotation.class) {
        TestAnnotation a = (TestAnnotation) anno;
        // TestAnnotationのvalueの値を出力
        System.out.println(a.value());
      }
    }
  }
}


上の例をみれば、Exampleでアノテーションの値を取得して設定されているvalueの値をコンソール出力に出力しました。


ここまでみればアノテーションのReflectionは別に活用度がなさそうです。

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
// アノテーション生成(メンバー変数用)
// RUNTIME設定しなければ実行する時、getDeclaredAnnotationsで探索ができない。
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface DependancyInjection {
  // 自動生成されるクラスタイプ。基本タイプはObject
  public Class<?> value() default Object.class;
}
// クラス
class Node {
  // コンストラクタ
  public Node() {  }
  // 関数生成
  public void print() {
    // コンソール出力
    System.out.println("Hello world");
  }
}
// 親クラス
class Abstract {
  // コンストラクタ
  public Abstract() {
    try {
      // メンバー変数を取得
      for (Field field : Example.class.getDeclaredFields()) {
        // DependancyInjectionアノテーション取得
        DependancyInjection anno = field.getDeclaredAnnotation(DependancyInjection.class);
        // あれば?
        if (anno != null) {
          // アクセス修飾子を無視
          field.setAccessible(true);
          // value関数値を取得
          Class<?> clz = anno.value();
          Constructor<?> constructor;
          // もしかしてObjectタイプなら
          if (clz == Object.class) {
            // メンバー変数のタイプを取得する。
            clz = field.getType();
          }
          // インスタンス生成
          constructor = clz.getConstructor();
          // 値を格納
          field.set(this, constructor.newInstance());
        }
      }
    } catch (Throwable e) {
      e.printStackTrace();
    }
  }
}
// 親クラスを継承する。
class Example extends Abstract {
  // 依存性注入のアノテーションがあるメンバー変数
  @DependancyInjection()
  private Node node1;
  // 依存性注入のアノテーションがないメンバー変数
  private Node node2;
  // 出力関数
  public void print() {
    // node1がnullではなければ
    if (this.node1 != null) {
      // print関数を呼び出す。
      this.node1.print();
    } else {
      // コンソール出力
      System.out.println("node1 null");
    }
    // node2がnullではなければ
    if (this.node2 != null) {
      // print関数を呼び出す。
      this.node2.print();
    } else {
      // コンソール出力
      System.out.println("node2 null");
    }
  }
  // 実行関数
  public static void main(String[] args) {
    // Exampleインスタンス生成
    Example ex = new Example();
    // 関数呼び出す。
    ex.print();
  }
}


上のソースをみればExampleクラスでメンバー変数を二つを宣言します。そして親抽象クラスのコンストラクタからDependancyInjectionのアノテーションを持っている変数に変数に関してインスタンス生成します。


結果でprint関数を呼び出すとnode1はnullではなく、print関数が呼び出せることを確認できます。

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
// アノテーション生成(メンバー変数用)
// RUNTIME設定しなければ実行する時、getDeclaredAnnotationsで探索ができない。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface AutoExecute {
  public int value() default 0;
}
// 親クラス
class Abstract {
  // コンストラクタ
  public Abstract() {
    try {
      // 関数を取得
      for (Method method : Example.class.getDeclaredMethods()) {
        // AutoExecuteのアノテーション取得
        AutoExecute anno = method.getDeclaredAnnotation(AutoExecute.class);
        // あれば?
        if (anno != null) {
          // アクセス修飾子を無視
          method.setAccessible(true);
          // 関数を実行
          method.invoke(this);
        }
      }
    } catch (Throwable e) {
      // エラーをコンソール出力
      e.printStackTrace();
    }
  }
}
// 親クラスを継承する。
class Example extends Abstract {
  // 自動実行するアノテーション設定
  @AutoExecute
  // 関数設定
  public void print() {
    // コンソール出力
    System.out.println("print");
  }
  // 自動実行するアノテーション設定
  @AutoExecute
  // 関数設定
  public void run() {
    // コンソール出力
    System.out.println("run");
  }
  // 自動実行するアノテーション設定
  @AutoExecute
  // 関数設定
  public void test() {
    // コンソール出力
    System.out.println("test");
  }
  // 関数設定
  public void close() {
    // コンソール出力
    System.out.println("close");
  }
  // 実行関数
  public static void main(String[] args) {
    new Example();
  }
}


上の関数はコンストラクタからAutoExecuteアノテーションを持っている関数を探して実行する関数です。

print関数とrun、testはアノテーションが設定されているので実行されることを確認できます。


元にアノテーションはメタデータの機能だけありますが、Reflectionと一緒に使えば依存性注入や実行パターンを設定する(戦略パターン、Facadeパターン)などの様々なパターンを設定することができます。


ここまでJavaのReflection機能を使う方法(Annotation編)に関する説明でした。


ご不明なところや間違いところがあればコメントしてください。

Study / Java」の他投稿
最新投稿