@DeclareParents

@DeclareParents は、一致する Bean のプロキシに追加のインターフェースを実装させます。実装はアスペクトが用意します(「イントロダクション」)。

なぜ必要か

多くの Bean に、そのクラスが持たない共通の機能を持たせたいことがあります。たとえば、各サービスが報告できる利用回数カウンターです。@DeclareParents は、クラスを変更せずにそのインターフェースをプロキシに追加します。

クラスにインターフェースを直接追加できる場合は不要です。そのほうが読みやすく、実行時のコストもありません。イントロダクションはまれに使う道具です。

仕組み

@Aspect クラスの static フィールド に @DeclareParents を付けます。フィールドの型が追加するインターフェースです。型パターンに一致するクラスを持つすべての Bean のプロキシがそのインターフェースを実装し、そのメンバーを defaultImpl のインスタンスに転送します。インスタンスはプロキシごとに 1 つ、最初に使われたときに作られます。

import 'package:spring_core_dart/spring_core_dart.dart';

// The capability to add.
abstract interface class UsageTracked {
  int get useCount;
  void increment();
}

// Its implementation: public, not abstract, with a no-argument constructor.
class DefaultUsageTracked implements UsageTracked {
  int _n = 0;

  @override
  int get useCount => _n;

  @override
  void increment() => _n++;
}

@Service()
class OrderService {
  void place(String isbn) {}
}

@Aspect()
@Component()
class UsageTracking {
  // Every OrderService (and subtype) proxy also implements UsageTracked.
  @DeclareParents('OrderService+', defaultImpl: DefaultUsageTracked)
  static UsageTracked? mixin;

  // `this(usage)` binds the proxy as a UsageTracked, so each call bumps
  // that bean's own counter.
  @Before('execution(* OrderService.*(..)) && this(usage)')
  void count(UsageTracked usage) => usage.increment();
}

導入したインターフェースを通してカウンターを読みます。

const log = SpringLog('app');

final context = ApplicationContext.run([appBeans]);
final orders = context.getBean<OrderService>();
orders.place('978-0');
final usage = orders as UsageTracked; // the proxy implements it
log.info('calls: ${usage.useCount}'); // calls: 1

パラメータ

名前 型 既定値 意味
value String 必須 インターフェースを得る Bean クラスの型パターン。* ワイルドカードを含む単純名で、+ はその型とサブタイプを表します(Orders、*Service、Repository+)。
defaultImpl Type 必須 インターフェースを実装するクラス。public で抽象でなく、フィールドのインターフェースを実装し、引数なしのコンストラクタを持つ必要があります。

ルールと制限

Spring (Java) では

@Aspect
@Component
class UsageTracking {
  @DeclareParents(value = "com.example.service.*+",
      defaultImpl = DefaultUsageTracked.class)
  public static UsageTracked mixin;

  @Before("execution(* com.example.service.*.*(..)) && this(usage)")
  void count(UsageTracked usage) { usage.increment(); }
}

重要な違い:

関連項目