@Pointcut とポイントカット式
ポイントカットは、アドバイスをどの Bean のどのメンバーに適用するかを表す式です。@Pointcut はその式に名前を付けます。
なぜ必要か
すべてのアドバイスのアノテーションはポイントカットを受け取ります(例: @Before('execution(* OrderService.*(..))'))。式は名前、型、アノテーション、Bean 名でメソッドを選びます。複数のアドバイスが同じ式を使う場合は、@Pointcut で一度だけ書き、名前で参照します。
式を一度しか使わない場合、@Pointcut は不要です。アドバイスのアノテーションに直接書いてください。
ポイントカットに名前を付ける
アスペクトの空のメソッドに @Pointcut を付けます。メソッド本体が呼ばれることはありません。同じアスペクトの他のポイントカットからは name() で参照します。別のアスペクトからは AspectClass.name() と書きます。
@Aspect()
@Component()
@Log()
class AuditAspect {
// Every OrderService member, named once.
@Pointcut('within(OrderService)')
void orders() {}
@Before('orders()')
void before(JoinPoint call) => log.info('calling ${call.toShortString()}');
// Combine a named pointcut with other designators.
@After('orders() && !execution(* *.slow*(..))')
void after() => log.info('done');
}
@Aspect()
@Component()
@Log()
class MetricsAspect {
// AuditAspect's pointcut, reused here with the class name.
@After('AuditAspect.orders()')
void count() => log.info('order call');
}
アスペクトのスーパークラスや mixin の名前付きポイントカットは継承されます。
| 名前 | 型 | 既定値 | 意味 |
|---|---|---|---|
expression |
String |
必須 | メソッド名が表すポイントカット式。 |
式の構文
ポイントカットは、1 つ以上の指示子を演算子で組み合わせたものです。
designator(pattern) [&& | || | ! designator(pattern) ...]
演算子: && または and、|| または or、! または not、および括弧。
指示子
| 指示子 | 一致するもの | 例 |
|---|---|---|
execution([modifiers] ret [Type.]name(params)) |
戻り値の型、宣言型、名前、パラメータで選ぶメソッド | execution(* OrderService.find*(..)) |
within(TypePattern) |
本体が一致するクラスにあるすべてのメンバー。オーバーライドされていない継承メンバーはスーパークラス内とみなされます。Type+ はサブタイプにも一致します |
within(*Repository)、within(Repository+) |
target(Type)、this(Type) |
その型またはサブタイプ(インターフェースを含む)の Bean のすべてのメンバー | target(PaymentGateway) |
args(types) |
引数がこれらの型を持つメソッド。宣言されたパラメータ型、なければ実行時の引数の型(.. は任意の個数) |
args(String, ..) |
@args(Annotation, ..) |
引数のクラスがそのアノテーションを持つメソッド | @args(Audited) |
bean(namePattern) |
その名前、エイリアス、または修飾子の値を持つ Bean のすべてのメンバー | bean(order*) |
@annotation(Annotation) |
そのアノテーションを持つメソッド。実装しているインターフェースメソッドに付いている場合も含みます | @annotation(Timed) |
@within(Annotation) |
本体がそのアノテーションを持つクラスにあるすべてのメンバー | @within(Service) |
@target(Annotation) |
クラスがそのアノテーションを持つ Bean のすべてのメンバー | @target(Service) |
name()、Type.name() |
同じアスペクト、またはクラス Type の名前付きポイントカット |
orders()、AuditAspect.orders() |
それ以外の指示子(cflow、if など)は "unknown designator" エラーになります。
パターン
- 名前の中の
*は任意の文字列に一致します:find*、*Service、*。 executionのパラメータでは、..は任意の個数(0 個を含む)、*はちょうど 1 個に一致します:(..)、()、(String, ..)、(*, int)。- 戻り値の型を最初に書きます。
*は任意の型です:execution(* *(..))、execution(void *(..))、execution(Future *(..))。 - メソッド名の前の型は省略できます。
execution(* find*(..))はすべての Bean のfind*メソッドに一致します。 withinとexecutionのType+は、その型とサブタイプを表します:execution(* Repository+.*(..))。- 型引数と
?は無視されます。Future<int>はFutureに、int?はintに一致します。 - 型名は単純名です。
com.x.ServiceはServiceを意味します。within(com.x..*)のようなパッケージのワイルドカードはエラーになり、黙ってすべてに一致することはありません。 executionの修飾子:public、package:metaの@protectedが付いたメンバーに一致するprotected、そして決して一致しないprivate(private メンバーはプロキシ化されないため)。それぞれ!で否定できます。throws句はエラーです。Dart のメソッドは例外を宣言しません。エラーは型付きの@AfterThrowingパラメータで絞り込んでください。
execution が一致しないもの
execution はメソッドにだけ一致します。明示的なものも public フィールドの暗黙のものも、ゲッターとセッターには一致しません。それらには within、bean、target、@within、@target、@annotation を使います。
メンバーがオーバーライドしているスーパークラスのメソッドにだけ X が付いている場合、@annotation(X) は一致しません。これは AspectJ と同じです。
例
| ポイントカット | アドバイス対象 |
|---|---|
execution(* *(..)) |
すべての Bean のすべてのメソッド |
within(OrderService) |
OrderService のすべて(ゲッターとセッターを含む) |
execution(* OrderService.*(..)) |
OrderService のメソッド |
execution(* find*(..)) |
任意の Bean の、名前が find で始まるメソッド |
execution(Future *(..)) |
Future を返すメソッド |
execution(void *(String, ..)) |
最初のパラメータが String の void メソッド |
target(PaymentGateway) |
PaymentGateway を実装するすべての Bean |
bean(order*) |
名前が order で始まる Bean |
@annotation(Timed) |
@Timed() が付いたメソッド |
@within(Service) |
@Service クラスのすべてのメンバー |
within(*Service) && !execution(* get*(..)) |
サービス。ただし get... メソッドを除く |
orders() || bean(payments) |
このアスペクトの名前付きポイントカット、または payments Bean |
アドバイスのパラメータへの値の束縛
@annotation(x)、@within(x)、@target(x)、@args(x)、this(x)、target(x)、args(x, ..) の中の小文字で始まる識別子は、アドバイスメソッドのパラメータを指します。そのパラメータには、アノテーションのオブジェクト、プロキシ、Bean、または引数が渡されます。
大文字で始まる名前は型です。int、double、num、bool、dynamic、void も型として扱われます。
// Receives the @Timed annotation object and the first argument.
@Around('@annotation(timed) && args(isbn, ..)')
Object? time(ProceedingJoinPoint joinPoint, Timed timed, String isbn) {
log.info('timing ${joinPoint.signature.name} for $isbn');
return joinPoint.proceed();
}
束縛された値はパラメータの型を持つ必要があり、そうでなければその呼び出しは一致しません。束縛や引数の検査を含むポイントカットは呼び出しごとに再検査されます。それ以外は起動時に一度だけ判定されます。
ポイントカットが束縛しないパラメータや、パラメータのない束縛名はビルドエラーになります。
コードでのポイントカット
アスペクトの外では、ExpressionPointcut が同じ構文を受け付けます。DefaultPointcutAdvisor Bean の中で使います。不正な式は FormatException を投げます。
// LoggingInterceptor is your own MethodInterceptor (see JoinPoint).
@Configuration()
class AopConfig {
@Bean()
PointcutAdvisor serviceLogging() => DefaultPointcutAdvisor(
ExpressionPointcut(
'service() && !execution(* *.slow*(..))',
// Named pointcuts are passed as a map here.
namedPointcuts: {'service': 'within(*Service) || @within(Service)'},
),
LoggingInterceptor(),
);
}
その他の既製のポイントカット(すべて AopPointcut を実装):
| クラス | 一致するもの |
|---|---|
TruePointcut() |
すべての Bean のすべてのメンバー |
NameMatchMethodPointcut(names) |
名前が * パターンのいずれかに一致するメンバー。ゲッターとセッターも含みます |
AnnotationMatchingPointcut<A>({onClass = false}) |
A が付いたメンバー。onClass: true なら A が付いたクラスのすべてのメンバー |
ComposablePointcut([pointcut]) |
ビルダー。union(other) は「または」、intersection(other) は「かつ」を追加します |
ExpressionPointcut(expression, {namedPointcuts, ...}) |
上記の式の構文 |
ルールを Dart で書くほうが簡単な場合は、AopPointcut を自分で実装します。唯一のメソッド bool matches(MethodSignature signature, String beanName) は、呼び出しごとではなく、起動時にプロキシ化される各 Bean のメンバーごとに一度実行されます。
// Advises the read-only members: find* methods and getters.
final class ReadOnlyPointcut implements AopPointcut {
const ReadOnlyPointcut();
@override
bool matches(MethodSignature signature, String beanName) =>
signature.name.startsWith('find') ||
signature.kind == MemberKind.getter;
}
Spring (Java) では
@Aspect
@Component
class AuditAspect {
@Pointcut("within(com.example.OrderService)")
void orders() {}
@Before("orders()")
void before(JoinPoint call) { ... }
@Before("@annotation(timed) && args(isbn, ..)")
void timed(Timed timed, String isbn) { ... }
}
重要な違い:
- 型名は単純名です。
com.x..*のようなパッケージのワイルドカードは拒否されます。 - サポートされる指示子:
execution、within、this、target、args、@args、bean、@annotation、@within、@target、名前付きポイントカット。cflow、cflowbelow、ifなどその他の AspectJ 指示子はサポートされません。 executionにthrows句はありません。protectedはpackage:metaの@protectedを意味します。- Dart ではゲッターとセッターは独立したメンバーです。Java の
get*メソッドと違い、executionはそれらに一致しません。 - 不正なポイントカットは起動時ではなくビルド時に失敗します。