@Aspect

@Aspect() は、メソッドがアドバイス(他の Bean の呼び出しの前、後、または周囲で実行されるコード)であるような Bean クラスを示します。

なぜ必要か

アスペクトは横断的なコード(ログ、計測、監査、チェック)を置く場所です。一度書けば、そのポイントカットがどの Bean のどのメソッドに適用するかを決めます。アドバイスを受ける Bean は変更されません。

次の場合、アスペクトは不要です。

アスペクトの宣言

アスペクトは 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 つです。アノテーションの値でこれを変えられます。

アドバイス対象のオブジェクトごとに状態を持たせたい場合に使います。このようなアスペクトは 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 つ。

ルール

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 { ... }

重要な違い:

関連項目