アプリケーションを開発しているときに、メソッド実行前に共通の処理を実行させたいという時があるかと思います。
共通関数を作ってそれを呼び出すことでも実現できますが、数が多いといちいちメソッド呼び出しを書くのが面倒です。
また、手動の作業となるので漏れが発生する可能性もあります。
そういったときにSpringAOPという機能を使用すれば、一律で処理の前後の共通処理を走らせることができます。
SpringAOPとは何か
SpringAOPを使用することで、共通処理をメソッド前後に実行することが可能になります。
例えば、メソッド前後にログを埋め込んで、デバッグに使用するといった使い方も可能です。
従来の概念でいうとインターセプトのような感じです。
SpringAOPの簡単な仕組み
Springはオブジェクト(Bean)をDIコンテナで管理しています。
このBeanを呼び出すときに、proxyというオブジェクトが生成されます。
Beanはすべてproxy経由で呼び出されます。
このproxyがBeanの呼び出し時に、AOPが登録されていないかを検索して、実行してくれます。
これによって、共通の処理をメソッド実行前後で一律に実行するよう設定ができるのです。
AOPの設定値
AOPは以下の3つの設定ができます。
Advice | 実行する処理 |
---|---|
Pointcut | 処理を実行する場所(クラスやメソッド) |
JoinPoint | 処理を実行するタイミング |
これらを設定することで、いつどんな処理をどこで呼び出すかを柔軟に設定できるわけです。
Pointcut
Pointcutには以下の種類が存在します。
execution | 正規表現を使ってクラスやメソッドを指定できる |
---|---|
bean | Bean名で指定できる |
@annotation | カスタムアノテーションで指定できる(メソッド単位指定) |
@within | カスタムアノテーションで指定できる(クラス単位指定) |
JoinPoint
JoinPointには以下の種類が存在します。
Before | 処理前 |
---|---|
After | 処理後 |
Around | 実行前後 |
AfterReturning | 正常終了時 |
AfterThrowing | 異常終了時 |
SpringAOPのサンプル
では、SpringAOPを使用するための簡単なサンプルを見ていきます。
execution(正規表現)指定のサンプル
まずは、execution(正規表現)指定のサンプルを見てみます。
AOPを定義するための適当なクラスを作成します。
ここでは、「AopSample」としておきます。
import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.springframework.stereotype.Component; @Aspect @Component public class AopSample { // PointcutのExecution指定サンプル // Beforeのサンプル @Before("execution(* *..*.*Controller.*(..))") public void beforeSample(JoinPoint joinPoint) { System.out.println("[Before]メソッド開始:" + joinPoint.getSignature()); } // Afterのサンプル @After("execution(* *..*.*Controller.*(..))") public void afterSample(JoinPoint joinPoint) { System.out.println("[After]メソッド終了:" + joinPoint.getSignature()); } // Aroundのサンプル @Around("execution(* *..*.*Controller.*(..))") public Object aroundSample(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("[Around]メソッド開始:" + joinPoint.getSignature()); try { // メソッド実行 Object result = joinPoint.proceed(); System.out.println("[Around]メソッド終了:" + joinPoint.getSignature()); return result; } catch(Exception e) { System.out.println("[Around]メソッド異常終了:" + joinPoint.getSignature()); e.printStackTrace(); throw e; } } // AfterReturningのサンプル @AfterReturning("execution(* *..*.*Controller.*(..))") public void afterReturningSample(JoinPoint joinPoint) { System.out.println("[AfterReturning]メソッド正常終了:" + joinPoint.getSignature()); } // AfterThrowingのサンプル @AfterThrowing("execution(* *..*.*Controller.*(..))") public void afterThrowingSample(JoinPoint joinPoint) { System.out.println("[AfterThrowing]メソッド異常終了:" + joinPoint.getSignature()); } }
Pointcutの指定は、「引数 パッケージ名.クラス名.メソッド名(引数)」になります。
上記の「* *..*.*Controller.*(..)」だと、戻り値指定なし、末尾にControllerとつくクラスのすべてのメソッド(引数の指定もなし)が実行されます。
詳しくは、正規表現の文法などを参照下さい。
Aroundだけ書き方が特殊なので注意が必要です。
execution以外の書き方
先ほど説明したようにPointcut(実行箇所)はexecutution以外の書き方も可能です。
execution以外の書き方のサンプルは以下になります。
// Pointcutをbean指定 // Bean名の末尾にControllerとつくものを対象 @Before("bean(*Controller)") public void beanSample(JoinPoint jp) { System.out.println("[Before - Bean名指定]メソッド開始:" + jp.getSignature()); } // Pointcutをアノテーション指定 // GetMappingアノテーションがついているメソッドを対象 @Before("@annotation(org.springframework.web.bind.annotation.GetMapping)") public void annotationSample(JoinPoint jp) { System.out.println("[Before - annotation指定]メソッド開始:" + jp.getSignature()); } // Pointcutをwithin指定 // Controllerアノテーションがついているクラスを対象 @Before("@within(org.springframework.stereotype.Controller)") public void withinSample(JoinPoint jp) { System.out.println("[Before - within指定]メソッド開始:" + jp.getSignature()); }
このように、PointcutとJoinPointを組み合わせることで、様々な条件で処理を割り込ませることができます。