アドバイスの作成1 - @Aspect @Pointcut
Spring AOP でのアドバイスには、@Aspect
および、@Component
を使用します。ここまでに作成してきたリポジトリーでのモデル保存に際して、ロギングするためのポイントカット、およびアドバイスを、それぞれ以下を参考に、作成します。
// src/main/java/io/github/yo1000/sss/aspect/RepositoryPointcut.java
package io.github.yo1000.sss.aspect;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class RepositoryPointcut {
@Pointcut("execution(* io.github.yo1000.sss.repository..MemoRepository+.save(..))")
public void save() {}
}
// src/main/java/io/github/yo1000/sss/aspect/LoggingAdvice.java
package io.github.yo1000.sss.aspect;
import io.github.yo1000.sss.model.Memo;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LoggingAdvice {
private static final Logger LOGGER = LoggerFactory.getLogger(LoggingAdvice.class);
@Before("io.github.yo1000.sss.aspect.RepositoryPointcut.save()")
public void beforeSave(JoinPoint joinPoint) {
LOGGER.info(joinPoint.getSignature().toLongString());
if (joinPoint.getArgs().length <= 0 || !(joinPoint.getArgs()[0] instanceof Memo)) {
LOGGER.error("Args is invalid.");
return;
}
Memo arg = (Memo) joinPoint.getArgs()[0];
LOGGER.info(String.format("arg.getMemo() : %s", arg.getMemo()));
LOGGER.info(String.format("arg.getAuthor() : %s", arg.getAuthor()));
}
}
アプリケーションを再起動して、改めて、http://localhost:8080/memo へアクセスし、適当な値を POST してみます。
2016-05-24 19:06:17.195 INFO 7667 --- [nio-8080-exec-3] i.g.yo1000.sss.aspect.LoggingAdvice : public abstract void io.github.yo1000.sss.repository.MemoRepository.save(io.github.yo1000.sss.model.Memo)
2016-05-24 19:06:17.195 INFO 7667 --- [nio-8080-exec-3] i.g.yo1000.sss.aspect.LoggingAdvice : arg.getMemo() : ドキュメント書く
2016-05-24 19:06:17.195 INFO 7667 --- [nio-8080-exec-3] i.g.yo1000.sss.aspect.LoggingAdvice : arg.getAuthor() : 銀次郎
POST した値がログに出力されたのが確認できました。
今回、@Before
で記述したものが、先のページで用語を確認した、ポイントカットにあたります。ポイントカット記述内の、execution
というキーワードには、「アドバイスを適用するメソッドに対する書式を記述する」という意味があります。今回の例を分解すると、以下のようになります。
これに一致するメソッド実行で、ポイントカットメソッドの設定されたアドバイスが適用される、という動き方をします。なお、ポイントカットメソッドを経由せず、アドバイスメソッドに、直接ポイントカット指定子を記述しても、問題なく動作します。
ポイントカット指定子は、execution
だけでもほとんどのポイントカットを表現できますが、Spring AOP では、さらに多くのポイントカット指定子が用意されています。
ポイントカット指定子
execution
のような、ポイントカット記述の種類を表現するキーワードは、ポイントカット指定子 (Pointcut Designators, PCD) と呼ばれます。Spring AOP では、いくつかのポイントカット指定子がサポートされているため、これらを簡単に見ていきます。
execution
指定したメソッド実行のジョインポイントに一致する、ポイントカットを表現するのに使用します。Spring AOP を使用するにあたっての主だったポイントカット指定子です。
書式は以下のとおりです。
execution(modifiers-pattern? ret-type-pattern
declaring-type-pattern?name-pattern(param-pattern)
throws-pattern?)
within
指定した型内のジョインポイントに一致する、ポイントカットを表現するのに使用します。単体でもポイントカットとして動作しますが、一般的には、他のポイントカット指定子と組み合わせて使用します。
this
指定した型と互換するインスタンス内のジョインポイントに一致する、ポイントカットを表現するのに使用します。単体でもポイントカットとして動作しますが、一般的には、他のポイントカット指定子と組み合わせて使用します。ただし、CGLib proxy によりインスタンスが生成される場合、これは動作しない。(すなわち、インターフェースに対してポイントカット指定子を記述した場合にのみ動作する。)
target
指定した型と互換するインスタンス内のジョインポイントに一致する、ポイントカットを表現するのに使用します。単体でもポイントカットとして動作しますが、一般的には、他のポイントカット指定子と組み合わせて使用します。
args
指定した型と互換する引数を持ったジョインポイントに一致する、ポイントカットを表現するのに使用します。多くの場合、単体では動作しません。単体で使用した場合、プロキシーできないターゲットオブジェクトも含まれてしまい、実行時例外が発生します。
@target
指定したアノテーションが設定された型と互換するインスタンス内のジョインポイントに一致する、ポイントカットを表現するのに使用します。多くの場合、単体では動作しません。単体で使用した場合、プロキシーできないターゲットオブジェクトも含まれてしまい、実行時例外が発生します。
@args
指定したアノテーションが設定された型と互換する型の引数を持ったジョインポイントに一致する、ポイントカットを表現するのに使用します。多くの場合、単体では動作しません。単体で使用した場合、プロキシーできないターゲットオブジェクトも含まれてしまい、実行時例外が発生します。
なお、引数に対して設定されたアノテーションは対象とならないため、取り違えのないように注意してください。対象となるのは、引数の型に対して設定されたアノテーションです。
@within
指定したアノテーションが設定された型内のジョインポイントに一致する、ポイントカットを表現するのに使用します。単体でもポイントカットとして動作しますが、一般的には、他のポイントカット指定子と組み合わせて使用します。多くの場合、単体では動作しません。単体で使用した場合、プロキシーできないターゲットオブジェクトも含まれてしまい、実行時例外が発生します。
@annotation
指定したアノテーションが設定されたメソッド実行のジョインポイントに一致する、ポイントカットを表現するのに使用します。
not
ポイントカット指定子に、論理否定を設定します。!
で記述することも可能です。
and
複数のポイントカット指定子同士を、論理 AND で結合します。&&
で記述することも可能です。
or
複数のポイントカット指定子同士を、論理 OR で結合します。||
で記述することも可能です。
( )
複数のポイントカット指定子をグルーピングします。not
や、and
、or
と組み合わせて使用します。
アノテーション
今回、新たに登場したアノテーション、および関連するアノテーションを簡単に説明しておきます。
@Aspect
アスペクトクラスに設定します。これはステレオタイプアノテーションではありません。アスペクトクラスにはポイントカットの定義と、アドバイスの実装の2種類が存在します。
@Component
既定責務に分類のないクラスに設定します。このアノテーションが設定されると、クラスが DI コンテナへの登録対象としてマークされます。
@Pointcut
ポイントカットのエイリアスを設定します。ポイントカットクラスには、ステレオタイプアノテーションは不要です。
@Before
ジョインポイントとなったメソッドの、実行前に処理したいアドバイスに設定します。
@AfterReturning
ジョインポイントとなったメソッドの、実行後に処理したいアドバイスに設定します。処理のタイミングは、return
の直後になります。
@AfterThrowing
ジョインポイントとなったメソッドの、実行後に処理したいアドバイスに設定します。処理のタイミングは、(catch
されない場合) throw
の直後になります。
@After
ジョインポイントとなったメソッドの、実行後に処理したいアドバイスに設定します。処理のタイミングは、return
や、throw
、finally
よりも後になります。
@Around
ジョインポイントとなったメソッドの、実行前後に処理したいアドバイスに設定します。つまり、このアノテーションを設定すると、処理をラップすることができます。
このアノテーションの設定されたアドバイスメソッドは、引数に org.aspectj.lang.JoinPoint
ではなく、org.aspectj.lang.ProceedingJoinPoint
を取ります。この引数で受けたオブジェクトで、#proceed()
または、#proceed(Object[])
を呼び出さないと、ラップした処理が呼び出されません。
この特徴を利用すると、キャッシュからデータを取り出して、本来の処理を呼び出さずに高速にレスポンスするようなアドバイスが作成できます。
サンプル
ここまでのコードサンプルは、以下より入手できます。
$ git clone -b chapter/8 https://github.com/yo1000/self-study-spring.git