アドバイスの作成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 や、andor と組み合わせて使用します。

アノテーション

今回、新たに登場したアノテーション、および関連するアノテーションを簡単に説明しておきます。

@Aspect

アスペクトクラスに設定します。これはステレオタイプアノテーションではありません。アスペクトクラスにはポイントカットの定義と、アドバイスの実装の2種類が存在します。

@Component

既定責務に分類のないクラスに設定します。このアノテーションが設定されると、クラスが DI コンテナへの登録対象としてマークされます。

@Pointcut

ポイントカットのエイリアスを設定します。ポイントカットクラスには、ステレオタイプアノテーションは不要です。

@Before

ジョインポイントとなったメソッドの、実行前に処理したいアドバイスに設定します。

@AfterReturning

ジョインポイントとなったメソッドの、実行後に処理したいアドバイスに設定します。処理のタイミングは、return の直後になります。

@AfterThrowing

ジョインポイントとなったメソッドの、実行後に処理したいアドバイスに設定します。処理のタイミングは、(catch されない場合) throw の直後になります。

@After

ジョインポイントとなったメソッドの、実行後に処理したいアドバイスに設定します。処理のタイミングは、return や、throwfinally よりも後になります。

@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

results matching ""

    No results matching ""