Java
アノテーション
リフレクション

アノテーションとリフレクションで共通処理を実現する

More than 3 years have passed since last update.

最近初めてアノテーションを自作したので、
勉強がてら、アノテーションとリフレクションで共通処理を実現する方法を記載します。

例として、Webアプリケーションでよくあるバリデーションを共通的に行う方法を取り上げようと思います。

①バリデーションを行いたいフィールドに対して付与するアノテーションクラスを定義します。

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

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface CommonValidation {

    String expression() default "";

}

@Retention(RetentionPolicy.RUNTIME)は、実行時にこのアノテーションが参照できるようにしています。
これを付けない(デフォルト(RetentionPolicy.CLASS))と、実行時に参照することが出来ません。
また、@Target(ElementType.FIELD)で、アノテーションを付けられる箇所を限定しています。
上記は付けなくても構いません。
ElementType.METHODにするとメソッド定義だけに該当アノテーションを付与できるように指定できます。

正規表現でフィールドを検証するので、String型のフィールドを定義します。
booleanやintegerのフィールドを定義して、
必須入力可否や桁数チェックを表現することも出来ます。

②エンティティクラスのフィールドにアノテーションを付与します。

Customer.java
public class Customer {

    public String id;

    @CommonValidation(expression = ".{1,20}")
    public String name;

    @CommonValidation(expression = "\\d{1,12}")
    public String tell;

}

上記は簡素化した会員のエンティティです。
以下のバリデーションを定義します。
・nameフィールドは20桁以内であること。
・tellフィールドは1~12桁の数値型であること

③バリデーション処理の定義とテスト実行

Executor.java
import java.lang.reflect.Field;

public class Executor {

    public static void main(String[] args) throws Throwable {
        Customer customer = new Customer();
        customer.name = "テスト";
        customer.tell = "1234567890123";

        boolean isValidResult = validate(customer);
        System.out.println(isValidResult);
    }

    public static boolean validate(Object obj) throws IllegalArgumentException, IllegalAccessException {
        Field[] fields = obj.getClass().getFields();
        boolean isCorrectData = true;
        for(Field f : fields) {
            if(f.isAnnotationPresent(CommonValidation.class)) {
                String expression = f.getAnnotation(CommonValidation.class).expression();
                if(expression.length() == 0) continue;
                String val = f.get(obj).toString();
                if(!val.matches(expression)) {
                    System.out.println(f.getName() + " value is " + val + ". not matches " + expression);
                    isCorrectData = false;
                }
            }
        }
        return isCorrectData;
    }
}

上記のvalidateメソッドの中で以下の処理を行っています。
1.エンティティクラスの全フィールドを取得
2.CommonValidationアノテーションが付与されているフィールドか確認
3.正規表現取得
4.正規表現とフィールドの値のマッチング処理

実行結果は以下の様になります。

tell value is 1234567890123. not matches \d{1,12}
false

また、メソッドにアノテーションを付与して、
共通的な処理の実行制御を行うことも出来て非常に強力なことも行えます。

※エンティティをpublicフィールドに出来ない場合は、validateの中で、Field.setAccessible(true)する必要があります。