
# アドバイス

アドバイスは、ポイントカットに一致した各呼び出しの決まったタイミング（前、後、または代わり）で実行されるアスペクトのメソッドです。

## なぜ必要か

アドバイスのアノテーションは、コードがいつ実行されるかを決めます。目的を果たせる最も弱いものを選んでください。チェックには `@Before`、後始末には `@After`、結果の参照には `@AfterReturning`、エラーの参照には `@AfterThrowing` を使います。`@Around` は、引数や結果を変える、呼び出しを省く、リトライする、時間を計るといった必要があるときだけ使います。

| アノテーション | 実行されるタイミング | パラメータ（注記がなければすべて省略可） | 結果を変えられるか |
|---|---|---|---|
| `@Before` | メソッドの前 | `JoinPoint` | いいえ。例外を投げると呼び出しが止まります |
| `@After` | 正常終了でも例外でも、メソッドの後（`finally` と同様） | `JoinPoint` | いいえ |
| `@AfterReturning` | 正常に戻った後 | `JoinPoint`、次に結果 | いいえ |
| `@AfterThrowing` | 例外を投げた後。エラーはその後再スローされます | `JoinPoint`、次にエラー | いいえ |
| `@Around` | メソッドの代わり。呼ぶかどうか、どう呼ぶかを決めます | `ProceedingJoinPoint`（必須） | はい |

パラメータは位置引数です。`JoinPoint` と `ProceedingJoinPoint` は型が完全に一致するもので判定されます。ポイントカットが束縛するパラメータ（[ポイントカット](pointcut.md) を参照）は名前で判定されます。それ以外のパラメータはビルドエラーになります。

以下の例はすべて、次のようなアスペクトクラスのメソッドです。

```dart
import 'dart:async';

import 'package:spring_core_dart/spring_core_dart.dart';

// Marker annotations used below.
class Timed {
  const Timed();
}

class Retry {
  const Retry();
}

@Aspect()
@Component()
@Log()
class OrderAudit {
  OrderAudit(this.shop); // Shop is another bean of the application

  final Shop shop;

  @Pointcut('within(OrderService)')
  void orders() {}

  // advice methods go here
}
```

## @Before

一致した各呼び出しの前に実行されます。メソッド本体の前に必要なチェックやログに使います。引数や結果は変えられません。例外を投げるとメソッドは実行されず、呼び出し側はそのエラーを受け取ります。

```dart
// Refuses orders while the shop is closed.
@Before('execution(* OrderService.place(..))')
void checkOpen(JoinPoint call) {
  if (!shop.isOpen) {
    throw StateError('shop closed, ${call.signature.name} refused');
  }
}
```

| 名前 | 型 | 既定値 | 意味 |
|---|---|---|---|
| `pointcut` | `String` | 必須 | アドバイス対象のメソッドを選ぶポイントカット式。 |

## @After

一致した各呼び出しの後に、正常終了でも例外でも、`finally` ブロックのように実行されます。後始末や「呼び出し完了」のログに使います。結果やエラーは変えられません。

```dart
// Logs the end of every OrderService call, even a failed one.
@After('orders()')
void after(JoinPoint call) => log.info('finished ${call.toShortString()}');
```

| 名前 | 型 | 既定値 | 意味 |
|---|---|---|---|
| `pointcut` | `String` | 必須 | アドバイス対象のメソッドを選ぶポイントカット式。 |

## @AfterReturning

一致した呼び出しが正常に戻った後に実行され、戻り値を受け取ります。結果の確認やログに使います。結果を置き換えることはできません。置き換えるには `@Around` を使います。

`returning` で指定したパラメータが値を受け取ります。`returning` がない場合は、`JoinPoint` でもポイントカットが束縛するものでもない最初のパラメータが受け取ります。パラメータの型はフィルターとして働き、値がその型を持つときだけアドバイスが実行されます。

```dart
// Runs only when the result is a double; other results pass by.
@AfterReturning('orders()')
void returned(JoinPoint call, double total) => log.info('total: $total');

// `timed` is bound by the pointcut, so `returning` names the result.
@AfterReturning('@annotation(timed)', returning: 'result')
void timedResult(Timed timed, Object? result) => log.info('$timed: $result');
```

| 名前 | 型 | 既定値 | 意味 |
|---|---|---|---|
| `pointcut` | `String` | 必須 | アドバイス対象のメソッドを選ぶポイントカット式。 |
| `returning` | `String?` | `null` | 戻り値を受け取るアドバイスのパラメータ。`null` の場合は、`JoinPoint` でもポイントカットが束縛するものでもない最初のパラメータ。 |

## @AfterThrowing

一致した呼び出しが例外を投げた後に実行され、そのエラーを受け取ります。失敗のログやカウントに使います。エラーはその後必ず再スローされるので、呼び出し側にも届きます。

`throwing` で指定したパラメータ、なければ `JoinPoint` でも束縛されたものでもない最初のパラメータがエラーを受け取ります。その型はフィルターとして働き、その型のエラーに対してだけアドバイスが実行されます。

```dart
// Runs only for StateError; other errors pass by.
@AfterThrowing('orders()')
void failed(JoinPoint call, StateError error) =>
    log.warning('${call.signature.name} failed: ${error.message}');
```

| 名前 | 型 | 既定値 | 意味 |
|---|---|---|---|
| `pointcut` | `String` | 必須 | アドバイス対象のメソッドを選ぶポイントカット式。 |
| `throwing` | `String?` | `null` | エラーを受け取るアドバイスのパラメータ。`null` の場合は、`JoinPoint` でもポイントカットが束縛するものでもない最初のパラメータ。 |

## @Around

一致した各呼び出しの代わりに実行され、本来のメソッドを実行するかどうか、どう実行するかを決めます。時間の計測、引数の変更、結果の置き換え、リトライ、メソッドの省略ができます。

アドバイスは `ProceedingJoinPoint` を受け取り、呼び出しの結果（通常は `proceed()` の戻り値）を返します。`proceed(args)` は別の引数でメソッドを実行します。リストの要素数は呼び出しの引数の数と同じでなければならず、違う場合は `ArgumentError` が投げられます。`proceed()` を呼ばなければメソッドは省略されます。もう一度呼べばリトライになります。

```dart
// Keeps the discount between 0 and 50 percent.
@Around('execution(* OrderService.discount(..))')
Object? clamp(ProceedingJoinPoint call) {
  final [percent] = call.args;
  return call.proceed([(percent! as int).clamp(0, 50)]);
}

// Retries a flaky call up to three times.
@Around('@annotation(Retry)')
Object? retry(ProceedingJoinPoint call) {
  for (var attempt = 1; ; attempt++) {
    try {
      return call.proceed();
    } on TimeoutException {
      if (attempt == 3) rethrow;
    }
  }
}
```

| 名前 | 型 | 既定値 | 意味 |
|---|---|---|---|
| `pointcut` | `String` | 必須 | アドバイス対象のメソッドを選ぶポイントカット式。 |

## 非同期メソッド

`Future` を返すメンバー（または値が Future である `FutureOr` を返すメンバー）の場合:

- `@After`、`@AfterReturning`、`@AfterThrowing` は、メソッドが戻ったときではなく、Future が完了したときにその値またはエラーとともに実行されます。
- `@Around` では、`proceed()` は Future を返します。それをそのまま返すか、アドバイスを `async` にして `await` し、値を扱います。
- `@Before` やその他のアドバイスも `async` にできます。呼び出しはそれを待ってから進みます。
- 同期メソッドに対して `async` な `@Before`、`@After`、`@AfterReturning`、`@AfterThrowing` を使うと、呼び出しは `AopInvocationException` で失敗します。アドバイスを同期にするか、メソッドが `Future` を返すようにしてください。

```dart
// Times every method that returns a Future, until the future completes.
@Around('execution(Future *(..))')
Future<Object?> timeAsync(ProceedingJoinPoint call) async {
  final watch = Stopwatch()..start();
  try {
    return await (call.proceed()! as Future<Object?>);
  } finally {
    log.info('${call.signature.name}: ${watch.elapsedMilliseconds} ms');
  }
}
```

## 順序

- 複数のアスペクト: アスペクトクラスの `@Order`（または `Ordered`）で決まります。値が小さいほど外側で、その `@Before` が最初に、`@After` が最後に実行されます。順序のないアスペクトは最後になります。
- 1 つのアスペクトの中: around、before、after、afterReturning、afterThrowing の順です。同じ種類のアドバイスは宣言順を保ちます。

## Spring (Java) では

```java
@Before("execution(* com.example.OrderService.place(..))")
void checkOpen(JoinPoint call) { ... }

@AfterReturning(pointcut = "orders()", returning = "total")
void returned(JoinPoint call, double total) { ... }

@AfterThrowing(pointcut = "orders()", throwing = "error")
void failed(JoinPoint call, IllegalStateException error) { ... }

@Around("execution(* com.example.OrderService.discount(..))")
Object clamp(ProceedingJoinPoint call) throws Throwable {
  Object[] args = call.getArgs();
  args[0] = Math.min(Math.max((int) args[0], 0), 50);
  return call.proceed(args);
}
```

重要な違い:

- ポイントカットは最初の位置引数です: `@AfterReturning('orders()', returning: 'total')`。`pointcut =` と `value =` の使い分けはありません。
- `returning` と `throwing` は省略できます。省略すると、最初の空いているパラメータが値またはエラーを受け取ります。
- `Future` を返すメソッドへのアドバイスは、Future の完了時に実行されます。Java にはこのような規則はありません。
- `proceed` は `Throwable` を宣言しません。エラーはそのまま伝わります。

## 関連項目

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