
# AOP

AOP（アスペクト指向プログラミング）は、他の Bean のメソッドの前、後、または周囲で独自のコードを実行する仕組みです。対象の Bean を編集する必要はありません。

## なぜ必要か

ログ出力、処理時間の計測、監査、権限チェック、リトライなど、多くのクラスに同時に関わる関心事があります。これらをすべてのメソッドに書くと、同じコードが繰り返され、本来の処理と混ざります。AOP では、そのコードを **アスペクト** に一度だけ書き、どのメソッドに適用するかを **ポイントカット** で指定します。

1 つのクラスにしか関係しないコードには AOP は不要です。そのクラスに書いてください。また AOP にはコストもあります。アドバイスが適用された Bean は生成されたプロキシに置き換えられ、Bean が自分自身を呼び出す場合はアドバイスが実行されません（[プロキシ](proxies.md) を参照）。

## 用語

| 用語 | 意味 | コード上の形 |
|---|---|---|
| アスペクト | 追加のコードを持つ Bean | `@Aspect()` と `@Component()` を付けたクラス |
| アドバイス | 追加のコード 1 つと、それが実行されるタイミング | `@Before`、`@After`、`@AfterReturning`、`@AfterThrowing`、`@Around` を付けたメソッド |
| ポイントカット | アドバイスを適用するメソッド | アノテーションに渡す文字列。例: `'@annotation(Timed)'` |
| ジョインポイント | 横取りされた 1 回の呼び出し | `JoinPoint` または `ProceedingJoinPoint` パラメータ |
| プロキシ | アドバイスを呼び出す生成済みのラッパー | ジェネレーターが書き出し、Bean の代わりに注入される |

## セットアップ

AOP は `spring_core_dart` に含まれています。`package:spring_core_dart/aop.dart`、またはそれをエクスポートしている `package:spring_core_dart/spring_core_dart.dart` をインポートします。Dart には実行時リフレクションがないため、プロキシはビルド時に `spring_core_dart_generator` が書き出します。パッケージはこれに依存し、`build_runner` を実行する必要があります。

```yaml
dependencies:
  spring_core_dart: ^0.7.1

dev_dependencies:
  build_runner: ^2.17.0
  spring_core_dart_generator: ^0.7.1
```

```bash
dart run build_runner build
```

## 短い例

`@Timed()` を付けたすべてのメソッドの処理時間を計測します。

```dart
// lib/src/beans.dart
import 'package:spring_core_dart/spring_core_dart.dart';
import 'package:my_app/spring_gen/src/beans.spring.dart'; // written by the generator

// The module: every class in this directory and below is scanned.
@ComponentScan()
final BeanModule appBeans = $appBeans;

// An annotation is a plain class with a const constructor.
class Timed {
  const Timed();
}

// The aspect: a bean whose @Around method runs instead of each
// method marked @Timed().
@Aspect()
@Component()
@Log()
class TimingAspect {
  @Around('@annotation(Timed)')
  Object? time(ProceedingJoinPoint call) {
    final watch = Stopwatch()..start();
    try {
      return call.proceed(); // runs the real method
    } finally {
      log.info('${call.signature.name}: ${watch.elapsedMilliseconds} ms');
    }
  }
}

// An ordinary bean. It does not know about the aspect.
@Service()
class Search {
  @Timed()
  List<String> find(String text) => ['dart', 'spring'];
}
```

```dart
// bin/main.dart
import 'package:my_app/src/beans.dart';
import 'package:spring_core_dart/spring_core_dart.dart';

void main() {
  final context = ApplicationContext.run([appBeans]);
  // getBean returns the proxy, so the advice runs and logs "find: 0 ms".
  context.getBean<Search>().find('d');
  context.close();
}
```

追加の設定は不要です。コンテキストは `@Aspect` または `Advisor` Bean を見つけた時点で AOP を有効にします。

## このセクションのページ

- [@Aspect](aspect.md) - アドバイスを持つ Bean の宣言、順序、インスタンスごとのアスペクト。
- [@Pointcut とポイントカット式](pointcut.md) - ポイントカットへの名前付けと、メソッドを選ぶ式の構文。
- [アドバイス](advice.md) - `@Before`、`@After`、`@AfterReturning`、`@AfterThrowing`、`@Around`。
- [@DeclareParents](declare-parents.md) - 一致する Bean のプロキシに追加のインターフェースを実装させる。
- [JoinPoint](join-point.md) - 横取りした呼び出しについてアドバイスが読める情報と `MethodInterceptor`。
- [プロキシ](proxies.md) - プロキシの作られ方、自己呼び出し、コードによるアドバイザー、制限。

## Spring (Java) では

```java
@Configuration
@EnableAspectJAutoProxy
@ComponentScan
class AppConfig {}

@Aspect
@Component
class TimingAspect {
  @Around("@annotation(com.example.Timed)")
  Object time(ProceedingJoinPoint call) throws Throwable {
    long start = System.nanoTime();
    try {
      return call.proceed();
    } finally {
      log.info(call.getSignature().getName() + ": "
          + (System.nanoTime() - start) / 1_000_000 + " ms");
    }
  }
}
```

重要な違い:

- Spring は実行時にプロキシを作ります（JDK 動的プロキシまたは CGLIB）。ここではジェネレーターがビルド時にプロキシクラスを書き出すため、変更のたびに `build_runner` を実行する必要があります。
- `@EnableAspectJAutoProxy` はありません。アスペクトまたはアドバイザーの Bean があれば十分です。
- ポイントカット内の型名は単純名です（`com.example.Timed` ではなく `Timed`）。Dart にはパッケージがありません。
- Dart のゲッターとセッター（public フィールドの暗黙のものを含む）にもアドバイスを適用できます。

## 関連項目

- [Bean](../beans/index.md)
- [@Component](../beans/component.md)
- [@Bean](../beans/bean.md)
- [ApplicationContext](../beans/application-context.md)
