[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と一緒で使えば単純なメタデータの機能だけではないです。

Copy!
 [Source view] Example.java
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は別に活用度がなさそうです。

Copy!
 [Source view] Example.java
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関数が呼び出せることを確認できます。

Copy!
 [Source view] Example.java
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」の他投稿
最新投稿