
# @DeclareParents

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

## なぜ必要か

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

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

## 仕組み

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

```dart
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();
}
```

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

```dart
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 で抽象でなく、フィールドのインターフェースを実装し、引数なしのコンストラクタを持つ必要があります。 |

## ルールと制限

- インターフェースを得るのは、アスペクトと同じモジュールの Bean だけです。
- Bean がインターフェースを得るのは、アスペクトが登録されている間だけです。条件やプロファイルでアスペクトを無効にすると、イントロダクションもなくなります。
- ラッパープロキシと生成されたサブクラスプロキシの両方で動作します。
- インターフェースは Bean クラスではなくプロキシにあります。`getBean` や注入で得たオブジェクトをキャストしてください。
- `@DeclareParents` のイントロダクションを持つ Bean は、ビルドで間引かれることはありません。
- 不正な `defaultImpl` は、"defaultImpl Bad must be a public, non-abstract class implementing Tracked" のようなメッセージでビルドを失敗させます。

## Spring (Java) では

```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(); }
}
```

重要な違い:

- 型パターンはパッケージではなく単純名を使います。
- パターンは最初の位置引数で、`defaultImpl` は必須の名前付き引数です。
- Dart ではフィールドは nullable です（`static UsageTracked? mixin;`）。代入されることはなく、ジェネレーターが読むだけです。
- イントロダクションは、アスペクトと同じモジュールの Bean にだけ適用されます。

## 関連項目

- [@Aspect](aspect.md)
- [ポイントカット](pointcut.md)
- [プロキシ](proxies.md)
