SpringFrameworkの特徴的な機能のひとつであるAOPについて書いていきます。
SpringFrameworkについての記事はこちら
AOPとは?
Aspect Oriented Programmingの略で、「アスペクト指向プログラミング」と呼ばれる。
AOPを使用することで、クラスを横断した処理(例外処理やロギング、トランザクション処理など)をビジネスロジックから分離するようにコードを記述することができるようになる。
これによって、複数の処理をシンプルに記述できたり、同じ処理をおこなうコードが色々な場所で記述されていることを防ぐことができる。
Spring AOPで使用する用語
Aspect
横断的な処理とそれを実行する場所を定義したモジュールのこと。
Springでは@Aspect
をクラスにつけることでそのクラスはAspectとして認識される。
AspectはDIコンテナに登録されている必要があるので、コンポーネントスキャン対象配下にクラスが存在する場合はクラスに@Component
をつけ、そうでない場合はJavaConfigクラスでBean登録する。
@Component @Aspect public class SampleAspect{ }
JoinPoint
横断的な処理を挿入する場所のこと。
メソッドやコンストラクタの実行前、メソッドやコンストラクタの実行後といったように実行されるタイミングのことを示す。
Advice
JoinPointで実行される横断的な処理のこと。
@Aspect
をつけたクラスのメソッドにアノテーションをつけることで、そのメソッドをAdviceとして実行させることができる。
Adviceを表すアノテーションの種類は、以下の5つ。
@Before
JoinPointの処理前に実行されるAdviceのこと。
@After
JoinPointの処理後に実行されるAdviceのこと。
メソッドの実行結果が正常・例外に関わらずメソッドの実行後に実行される。
@AfterReturning
JoinPointの処理が正常終了時に実行されるAdviceのこと。
JoinPointで例外がスローされた場合は無効となる。
returningパラメータで文字列を指定すると、その文字列の変数にJoinPointで実行された処理の戻り値が格納される。これによって戻り値をAdviceで利用することができる。
@AfterReturning("execution(* jp.co.sample..*(..))", returning = "r") public void after(Object r) { }
@AfterThrowing
JoinPointで例外が発生した後に実行されるAdviceのこと。
throwingパラメータで文字列を指定すると、その文字列の変数にJoinPointで実行された処理で起きた例外が格納される。これによって例外をAdviceで利用することができる。
@AfterThrowing("execution(* jp.co.sample..*(..))", throwing = "t") public void after(Throwable r) { }
@Around
JoinPointの前後で実行されるAdviceのこと。
実行タイミングは自身で定義できる等、最も汎用的なAdvice。
@Around("execution(* jp.co.sample..*(..))") public void around(ProceedingJoinPoint pjp) throws Throwable { // 前処理 //メソッド実行 Object result = pjp.proceed(); // 後処理 }
PointCut
処理がJoinPointに到達した時、Adviceを実行するかどうかを判定するもの。
例えば、「メソッド名がgetで始まる時だけAdviceを実行する」のような条件を定義したもの。
下のコード例のようにAdviceのアノテーションのパラメータとしてPointCutを表現する。
@After("execution(* jp.co.sample..*(..))") public void after(){ }
PointCutの書き方は以下の通り
execution(修飾子 戻り値 パッケージ名.クラス名.メソッド名(引数の型) throws 例外の型
修飾子・パッケージ名・クラス名・例外の型は省略可能。
Spring AOPは publicメソッドのみサポート なので修飾子は記述する必要はない。
executionとは ポイントカット指示子(メソッド実行条件) と呼ばれるもので、Spring AOPでは最もよく利用される。
execution()以外にもwithin()やtarget()など存在する。(後述)
PointCutには具体的なメソッドを指定してもよいが、「*
」と「..
」を使用することで、対象のメソッドに幅を持たせることができる。
AOPの対象とするクラスもDIコンテナに登録されている必要がある。DIコンテナに登録されていない場合は、PointCutの条件に合致していてもAdviceは実行されない。
記号 | 説明 |
---|---|
* |
任意の文字列を表す。パッケージ名で使う場合は、「任意の名前の1パッケージ」を表し、引数で使う場合は「任意の型1つ」を表す。 |
.. |
パッケージ名で使う場合は、「0以上のパッケージ」を表し、引数で使う場合は「0以上の任意の型」を表す。 |
以下にPointCutの具体的な設定例を示す
publicメソッドの実行前 @Before("execution(public * *(..))") メソッド名が"set"で始まるメソッドの実行前 @Before("execution(* set*(..))") MyServiceで定義されているメソッドの実行前 @Before("execution(* jp.co.sample.MyService.*(..))") sampleパッケージに定義されているメソッドの実行前 @Before("execution(* jp.co.sample..(..))") sampleもしくはそのサブパッケージ内に定義されているメソッドの実行前 @Before("execution(* jp.co.sample...(..))")
ポイントカット指示子
上で紹介したexecution()以外の指示子について説明する。
within
指定したクラス(型)で定義されたメソッドに対する呼び出しに適用するポイントカット。
以下のように設定した場合はExampleServiceで定義されたメソッドに対する呼び出しに適用される。
@Before("within(com.example.service.ExampleService)")
target
指定したクラス(型)のインスタンス(その型を実装するインスタンス)のメソッド呼び出しに適用するポイントカット。
例えばExampleServiceがParentExampleServiceを継承している場合、以下の設定例では、ExampleService及びParentExampleServiceで定義されたメソッド呼び出しに適用される
(withinでは指定したクラスのみで親クラス、子クラスの呼び出しには適用されない)。
@Before("target(com.example.service.ParentExampleService)")
args
指定した引数の型にマッチするメソッド呼び出しに適用するポイントカット。
例えば以下のようにすることで、ExampleServiceクラスでString型の引数を取るメソッド呼び出しに適用される。
@Before("within(com.example.service.ExampleService) && args(java.lang.String)") public void beforeArgs() {
また、以下のようにargsに実際の引数名を指定することでAdvice(ここではbeforeArgsメソッド)の引数としてバインディングされ、Advice内で利用することができる。
@Before("within(com.example.service.ExampleService) && args(something)") public void beforeArgs(String something) {
@annotation
指定されたアノテーションが付与されたメソッド呼び出しに適用するポイントカット。
例えば以下のようにすることで、ExampleServiceクラスで@Beanアノテーションが付与されたメソッド呼び出しに適用される。
@Before("within(com.example.service.ExampleService) && @annotation(org.springframework.context.annotation.Bean)")
その他のポイントカット指示子
以下のサイトに説明がされている。
5.2.3. Declaring a pointcut
Spring AOP ポイントカット指定子の書き方について
Introduction to Pointcut Expressions in Spring
SpringBootにはControllerAdvice, Interceptorといったものも存在している。
ControllerAdviceについての記事はこちら: olafnosuke.hatenablog.com
Interceptorについての記事はこちら: olafnosuke.hatenablog.com