こんにちは。 エキサイト株式会社の三浦です。
こちらは、エキサイトホールディングス Advent Calendar 2023の6日目の記事になります。
良ければ他の記事もどうぞ!
さて、コーディングをしている時、「このメソッドが実行される際はログを取りたい」など、メソッド実行時に追加で何かをさせたいという要件はたまに存在します。
その際には「AOP」という方法が役に立つのですが、今回はSpring Bootにおいて、独自のアノテーションを目印にしてAOPを行う方法を紹介します。
AOPとは
AOPは、「Aspect Oriented Programming(アスペクト指向プログラミング)」のことです。
上記で上げた「メソッド実行時にログを取る」などの共通化可能な処理を抜き出して、通常の処理と同等に書かなくても、メソッド名やアノテーションなどを目印に裏側で実行してくれるようにするというものです。
通常
通常は、以下のようにメソッド内に毎回ログ用の処理を書く必要があります。
public void method1(String arg1, String arg2) { // ログ用処理 this.logMethod(arg1, arg2); this.mothod1Process(arg1, arg2); } public void method2(String arg1, String arg2) { // ログ用処理 this.logMethod(arg1, arg2); this.mothod2Process(arg1, arg2); }
AOPを使えば、特定のアノテーション等を目印にして、裏側でログを取ることが可能です。
// このアノテーションを目印にログを取る @LogMethod public void method1(String arg1, String arg2) { this.mothod1Process(arg1, arg2); } // このアノテーションを目印にログを取る @LogMethod public void method2(String arg1, String arg2) { this.mothod2Process(arg1, arg2); }
今回の例のように、使用箇所が少なかったり同一クラス内でしか使わない場合は、そこまでメリットが感じられないかもしれません。
しかし、使用箇所が増えたり、クラスを横断してその処理が必要になっていくにつれ、AOPによる共通化の恩恵は大きくなっていきます。
では、Spring Bootでは具体的にどのように実装すれば良いのでしょうか。
Spring BootでAOPを使う
今回は簡単に、以下の要件を考えます。
- メソッド実行前に、メソッド名やクラス名、その引数をログに取る
- 独自のアノテーションが付いたメソッドでのみログを取る
build.gradle
まずは build.gradle
に以下の設定を行います。
なお、Spring Bootはすでに読み込んでいる前提です。
implementation 'org.springframework.boot:spring-boot-starter-aop'
独自アノテーション
次に、目印とする独自アノテーションを作成します。
@Target(ElementType.METHOD) // メソッドのみに付与可能 @Retention(RetentionPolicy.RUNTIME) @Documented public @interface LogMethod { }
AOPの処理本体
続いて、AOPの処理本体を設定します。
@Aspect @Component public class LogMethodAspect { @Before("@annotation(LogMethod)") // @LogMethodが付いているメソッドが実行された時、処理が実行される直前に以下が行われる public void beforeLogMethod(JoinPoint joinPoint) { // クラス名とメソッド名を取得 final String path = "{className}.{methodName}" .replace("{className}", joinPoint.getSignature().getDeclaringTypeName()) .replace("{methodName}", joinPoint.getSignature().getName()); // 引数を取得 final String body = Arrays.toString(joinPoint.getArgs()); // 取得した情報を、何かしらに出力する this.log(path, body); } }
AOPを適用
ここまで来れば、後は使うだけです。
@Component public class SampleClass { @LogMethod public void method1(String arg1, String arg2) { this.mothod1Process(arg1, arg2); } @LogMethod public void method2(String arg1, String arg2) { this.mothod2Process(arg1, arg2); } }
これで無事、ログを取ることができます!
呼び出し例
// DIコンテナから呼び出し sampleClass.method1("hello", "world");
ログ
今回は「ログを取る」ことを目的としましたが、もちろんそれ以外の処理を行わせることもできます。
また、
- 特定のパッケージのメソッドを対象にする
- 特定のメソッド名のメソッドを対象にする
であったり、
- メソッド実行後に処理を行う
- メソッド実行の前後に処理を行う
など、色々なやり方があります。
ぜひいろいろ試してみてください!
最後に
コードが複雑になればなるほど、AOPはその真価を発揮していきます。
一度理解してしまえば使いたくなること請け合いなので、使えそうな場面があったらぜひやってみてください!