【Spring Boot】アノテーションとAOPで関数に前処理を差し込む

Created 2020年2月20日22:29
Updated 2020年2月23日19:06
Categories Java Spring Boot

Spring BootでControllerの関数に前処理を挟みたくて色々と調べた結果をまとめておきます。

結論から言うとアノテーションを作ってAOPでアノテーションを対象にしたAspectを作ればがっつり関数の挙動を変えられましたって話です。

Javaのアノテーションについて

私は当初JavaのアノテーションはPythonのデコレータみたいなのだと思ってたのですが、調べてみるともっと簡易的な目的で使われるようです。

具体的には、メソッドやクラスに追加の属性値を付与したり、簡単なバリデーションなどをするのに使われているようで、関数の前後に割り込んでがっつり処理内容を変えることは難しそうでした。

例えば、メソッドを対象にしたアノテーションはこんな感じで作れます。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Test {
    String someAttr() default "test";
}

ネットで使用例を調べても何が便利なのか分からないものしか見つけられず、正直これをどう活かしてよいかまだあんまり分かっていません。

有効な使用例についてはまた別途調べていきたいと思います。

AOPと組み合わせる

Spring BootにはAOPという機能があり、特定の関数の実行の前後に処理を挟み込むようなことができます。

AOPの実行対象にはアノテーションも含めることができるため、組み合わせることでアノテーションを付与した関数に対して前後処理を行うことができます。

今回は試しにコントローラの関数に対してアノテーションを付与して、

  1. アノテーションに渡した引数と
  2. 関数自体に渡された引数で処理を分岐させ
  3. 分岐次第で関数を実行せず別の値(レスポンス)を返し、
  4. それ以外は普通に関数の実行結果を返す

という鬼のように複雑な処理を実装してみたいと思います。

まずは対象となるコントローラの関数を作ってみます。

    @GetMapping
    public String getTest(@RequestParam("email") String email) {
        return email;
    }

普通にメールアドレスを返すだけの関数です。

これに、例えば特定のドメインが含まれる時だけ関数を実行するような前処理を行うアノテーションを作成します。

@Aspect
public class TestAspect {

    // 付与するアノテーション
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface OverrideMethod {
        String targetDomain();
    }

    // 割り込む処理を記述
    @Around("@annotation(com.sakaki333.test.TestAspect.OverrideMethod)")
    public String overrideMethodImpl(ProceedingJoinPoint joinPoint) throws Throwable {
        String email;
        Object[] args = joinPoint.getArgs();
        for (Object arg : args) {
            if (arg instanceof String) {
                email = (String) arg;
            }
        }
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        OverrideMethod instance = signature.getMethod().getAnnotation(OverrideMethod.class);
        if (email.contains(instance.targetDomain)) {
            return (String) joinPoint.proceed(args);
        } else {
            return "error";
        }
    }
}

最後に、作ったアノテーションを対象の関数に付ければ完了です。

    @GetMapping
    @OverrideMethod(targetDomain = "@gmail.com")
    public String getTest(@RequestParam("email") String email) {
        return email;
    }

OverrideMethod アノテーションが付与された関数すべてで実行されるため、getTest関数が実行される前にoverrideMethodImpl関数が実行されます。

joinPoint.getArgs() で関数に渡された引数を取得しています。

引数は全てObject型で来るため、String型のものをemailとしてキャストしています(String型の引数が複数あったらVOとかにするのが良いと思います)。

signature.getMethod().getAnnotation(OverrideMethod.class) でアノテーションに設定された値を取得し、emailに対象のドメインが含まれるか検証しています。

所感

アノテーションは想像よりできることが少なく驚きましたが、変に引数とか改変しないためお行儀は良いと思いました。

逆にAOPは自由度が高すぎて、使いどころを気を付けないといろいろ破壊しそうで怖いですね・・・

やはりSpring Bootは奥が深いです。もっと書いて慣れていかないと。。

コメントを投稿

コメント