@Aspect
@Aspect() は、メソッドがアドバイス(他の Bean の呼び出しの前、後、または周囲で実行されるコード)であるような Bean クラスを示します。
なぜ必要か
アスペクトは横断的なコード(ログ、計測、監査、チェック)を置く場所です。一度書けば、そのポイントカットがどの Bean のどのメソッドに適用するかを決めます。アドバイスを受ける Bean は変更されません。
次の場合、アスペクトは不要です。
- 追加のコードが 1 つのクラスにしか関係しない場合。そのクラスに書いてください。
- アドバイスがコードで選ぶ再利用可能なオブジェクトである場合(ライブラリなど)。代わりに
PointcutAdvisorBean を宣言します(プロキシ を参照)。
アスペクトの宣言
アスペクトは Bean でもある必要があります。@Component() を付けるか、@Bean メソッドから返します。通常の Bean と同じように他の Bean を注入できます。アスペクト自身がプロキシ化されることはありません。
import 'package:spring_core_dart/spring_core_dart.dart';
// A plain bean that stores audit lines.
@Component()
class AuditTrail {
final lines = <String>[];
void record(String line) => lines.add(line);
}
// Records every call to the order service.
@Aspect()
@Component()
class AuditAspect {
AuditAspect(this.trail); // aspects inject other beans as usual
final AuditTrail trail;
@Before('execution(* OrderService.*(..))')
void audit(JoinPoint joinPoint) =>
trail.record('${joinPoint.toShortString()} ${joinPoint.args}');
}
アスペクトのメソッドには、アドバイスのアノテーション(@Before、@After、@AfterReturning、@AfterThrowing、@Around)を 1 つずつ付けます(アドバイス を参照)。@Pointcut を付けた空のメソッドはポイントカットに名前を付けます(ポイントカット を参照)。アスペクトのスーパークラスや mixin のアドバイスと @Pointcut メソッドは継承されます。
複数のアスペクトの順序
複数のアスペクトが同じ呼び出しにアドバイスする場合は、アスペクトクラスの @Order、または Ordered の実装で決まります。値が小さいほど外側になり、その @Before が最初に、@After が最後に実行されます。順序のないアスペクトは最後になります。
// Security checks run outside timing, so a refused call is not timed.
@Aspect()
@Component()
@Order(1)
class SecurityAspect {
@Before('@within(Service)')
void check(JoinPoint joinPoint) {
// throw to refuse the call
}
}
@Aspect()
@Component()
@Order(2)
@Log()
class TimingAspect {
@Around('@within(Service)')
Object? time(ProceedingJoinPoint call) {
final watch = Stopwatch()..start();
try {
return call.proceed();
} finally {
log.info('${call.toShortString()} ${watch.elapsedMilliseconds} ms');
}
}
}
1 つのアスペクトの中では、アドバイスは種類ごとに入れ子になります。around、before、after、afterReturning、afterThrowing の順です。同じ種類のアドバイスは宣言順を保ちます。
インスタンスごとのアスペクト
既定では、アプリケーション全体でアスペクトのインスタンスは 1 つです。アノテーションの値でこれを変えられます。
perthis(pointcut): プロキシごとに 1 つのインスタンス。pertarget(pointcut): ターゲット Bean ごとに 1 つのインスタンス。
アドバイス対象のオブジェクトごとに状態を持たせたい場合に使います。このようなアスペクトは prototype Bean(@Scope('prototype'))である必要があります。アドバイスは、per 句のポイントカットとアドバイス自身のポイントカットの両方に一致する場所にだけ適用されます。
// Counts the calls of each Orders proxy separately.
@Aspect('perthis(execution(* Orders.*(..)))')
@Component()
@Scope('prototype')
class CallCounter {
int calls = 0;
@Before('execution(* Orders.*(..))')
void count() => calls++;
}
singleton のインスタンスごとのアスペクト、percflow、pertypewithin はビルドエラーになります。
パラメータ
| 名前 | 型 | 既定値 | 意味 |
|---|---|---|---|
value |
String |
'' |
インスタンス化モデル。空: 共有インスタンス 1 つ。perthis(pointcut): プロキシごとに 1 つ。pertarget(pointcut): ターゲット Bean ごとに 1 つ。 |
ルール
@Aspect()だけでは Bean になりません。@Component()または@Beanメソッドを追加します。- ジェネレーターを実行する必要があります(
dart run build_runner build)。ジェネレーターはアスペクトのすべてのポイントカットを検査し、構文エラーがあればアスペクト、アドバイス、位置を示してビルドを失敗させます。 - 親コンテキストのアスペクトは、子コンテキストの Bean にも適用されます。
- 条件やプロファイルで無効になったアスペクトは登録されず、そのアドバイスは適用されません。
Spring (Java) では
@Aspect
@Component
@Order(1)
class AuditAspect {
@Before("execution(* com.example.OrderService.*(..))")
void audit(JoinPoint joinPoint) {
log.info(joinPoint.toShortString());
}
}
@Aspect("perthis(execution(* com.example.Orders.*(..)))")
@Component
@Scope("prototype")
class CallCounter { ... }
重要な違い:
@EnableAspectJAutoProxyはありません。アスペクトまたはアドバイザーの Bean があれば、コンテキストが自動プロキシ生成器を登録します。percflowとpertypewithinはサポートされません。- ポイントカットはパッケージ修飾名ではなく単純な型名を使います。