
# JoinPoint

`JoinPoint` は、横取りされた 1 回の呼び出し（どのメンバーが、どの Bean で、どの引数で呼ばれたか）を表します。

## なぜ必要か

アドバイスは、何に対して実行されているかを知る必要がよくあります。ログ行を書く、メソッド名でメトリクスに名前を付ける、メソッドのアノテーションを読む、といった場合です。`JoinPoint` パラメータを宣言すると、プロキシがそれを渡します。`@Around` アドバイスは、呼び出しを実行することもできる `ProceedingJoinPoint` を受け取ります。

すべての呼び出しで同じことをするアドバイス（`@After('orders()') void after() => ...` など）では不要です。このパラメータは `@Around` 以外のすべてのアドバイスで省略できます。

## JoinPoint

```dart
// Logs each call to the `calculator` bean with its arguments.
@Before('bean(calculator)')
void before(JoinPoint joinPoint) =>
    log.info('${joinPoint.toShortString()} with ${joinPoint.args}');
```

| メンバー | 型 | 意味 |
|---|---|---|
| `proxy` | `Object` | 呼び出し側が呼んだプロキシ。他の Bean が保持しているオブジェクトです。生成されたサブクラスプロキシでは Bean 自身です。 |
| `target` | `Object` | メンバーが実行される Bean。プロキシの背後にあるオブジェクトです。 |
| `signature` | `MethodSignature` | 呼ばれたメンバー。名前、型、アノテーション。 |
| `args` | `List<Object?>` | 引数。位置引数、次に名前付き引数の順です。コピーなので、変更しても呼び出しは変わりません。 |
| `toShortString()` | `String` | ログ用の短い説明。例: `execution(Greeter.greet(..))`。 |

## ProceedingJoinPoint

`@Around` アドバイスのジョインポイントです。メソッドが 1 つ加わります。

| メンバー | 型 | 意味 |
|---|---|---|
| `proceed([List<Object?>? args])` | `Object?` | 次のアドバイス、または最後のアドバイスの後ではメンバー自身を実行し、その結果を返します。`args` は引数を置き換え、呼び出しと同じ数の要素を持つ必要があります。違う場合は `ArgumentError` が投げられます。`Future` を返すメンバーでは、結果はその Future です。 |

```dart
// Keeps the second factor of `multiply` between 0 and 10.
@Around('execution(* Calculator.multiply(..))')
Object? clamp(ProceedingJoinPoint joinPoint) {
  final [a, b] = joinPoint.args;
  return joinPoint.proceed([a, (b! as int).clamp(0, 10)]);
}
```

## MethodSignature

呼び出さずに分かる、アドバイス対象のメンバーの情報です。Dart には実行時リフレクションがないため、ジェネレーターがプロキシ化される各メンバーに定数を 1 つ書き出します。型名は型引数のない単純なクラス名です（`Future<int>` ではなく `Future`）。ビルド時に書き出されるので、minify や難読化されたビルドでも動作します。アプリケーションはこれを読むだけで、作成はしません。

```dart
// Logs lines like "int Calculator.add(int, int)".
@Before('execution(* Calculator.*(..))')
void before(JoinPoint joinPoint) {
  final signature = joinPoint.signature;
  log.info('${signature.returnType} ${signature.declaringType}.'
      '${signature.name}(${signature.parameterTypes.join(', ')})');
}
```

| メンバー | 型 | 意味 |
|---|---|---|
| `name` | `String` | メンバーの名前。例: `add`。セッターの名前に `=` は付きません。 |
| `kind` | `MemberKind` | `method`、`getter`、`setter` のいずれか。 |
| `declaringType` | `String` | メンバーを宣言している最も抽象的な型。例: インターフェース `Calculator`。 |
| `declaringTypes` | `List<String>` | メンバーを宣言している Bean のすべての型。 |
| `targetType` | `String` | Bean のクラス。例: `SimpleCalculator`。 |
| `targetTypes` | `List<String>` | Bean クラスとそのすべてのスーパータイプ。 |
| `returnType` | `String` | 戻り値の型の名前。何も返さない場合は `void`、不明な場合は `dynamic`。 |
| `parameterTypes` | `List<String>` | パラメータの型の名前。 |
| `parameterNames` | `List<String>` | パラメータの名前。`args` と同じ順序です。 |
| `parameterAnnotations` | `List<List<Object>>` | 各パラメータのアノテーション。 |
| `annotations` | `List<Object>` | `@annotation(...)` が一致の対象とするメンバーのアノテーション。実行されるメソッドと、それが実装するインターフェースメソッドのものです。 |
| `mergedAnnotations` | `List<Object>` | メンバーと、それがオーバーライドまたは実装するすべての宣言のアノテーション。具体的なものが先です。 |
| `targetAnnotations` | `List<Object>` | Bean クラスのアノテーション。 |
| `implementingType` | `String` | メンバーの本体があるクラス。Bean クラス、またはメンバーを継承しているスーパークラスや mixin。 |
| `isProtected` | `bool` | メンバーに `@protected` が付いているかどうか。 |
| `toShortString()` | `String` | 例: `Greeter.greet(..)`。 |

## MemberKind

Dart ではゲッターとセッターは独立したメンバーで、public フィールドは暗黙のゲッター（`final` でなければセッターも）です。プロキシはそれらにもメソッドと同じようにアドバイスします。プロパティアクセスを別扱いしたい場合は `signature.kind` を読みます。

```dart
// Logs the calls to Account, but not the getter reads.
@Before('within(Account)')
void audit(JoinPoint joinPoint) {
  if (joinPoint.signature.kind != MemberKind.getter) {
    log.info(joinPoint.toShortString());
  }
}
```

| 値 | 意味 |
|---|---|
| `MemberKind.method` | メソッド。`execution(...)` が一致するのはこの種類だけです。 |
| `MemberKind.getter` | ゲッター。明示的なもの、または public フィールドの暗黙のもの。 |
| `MemberKind.setter` | セッター。明示的なもの、または final でない public フィールドの暗黙のもの。 |

## MethodInterceptor と MethodInvocation

アドバイスはアスペクトのメソッドではなくクラスとして書くこともできます。`MethodInterceptor` を実装し、`DefaultPointcutAdvisor` Bean でポイントカットと組み合わせます。ライブラリのキャッシュやセキュリティチェックのような、再利用可能なアドバイスに使います。

その `invoke` メソッドは `MethodInvocation` を受け取ります。これは `args` が実際の引数そのものである `ProceedingJoinPoint` です。インターセプターは `proceed()` を呼ぶ前に、引数をその場で変更できます。

```dart
// Upper-cases every String the slogans bean returns.
class UpperCase implements MethodInterceptor {
  @override
  Object? invoke(MethodInvocation invocation) =>
      (invocation.proceed()! as String).toUpperCase();
}

@Configuration()
class AopConfig {
  @Bean()
  PointcutAdvisor upperCase() =>
      DefaultPointcutAdvisor(ExpressionPointcut('bean(slogans)'), UpperCase());
}
```

| メンバー | 型 | 意味 |
|---|---|---|
| `MethodInterceptor.invoke(MethodInvocation invocation)` | `Object?` | 呼び出しの周囲で実行され、その結果を返します。続行するには `invocation.proceed()` を呼びます。呼ばなければメンバーは省略されます。 |
| `DefaultPointcutAdvisor(pointcut, advice, {order})` | - | ポイントカット、インターセプター、順序の組。`order` の既定値は `Ordered.lowestPrecedence`（最も内側）です。`Ordered` を実装しているため、その `@Bean` メソッドの `@Order` は無視されます。 |

## Spring (Java) では

```java
@Before("execution(* com.example.Calculator.*(..))")
void before(JoinPoint joinPoint) {
  MethodSignature signature = (MethodSignature) joinPoint.getSignature();
  log.info(signature.getReturnType() + " " + signature.getName()
      + Arrays.toString(joinPoint.getArgs()));
}
```

重要な違い:

- ゲッターメソッドではなくプロパティです: `getSignature()`、`getArgs()` ではなく `joinPoint.signature`、`joinPoint.args`。
- `MethodSignature` は型名を文字列で持ちます。`Class` オブジェクトや `java.lang.reflect.Method` ではありません。
- Dart のゲッターとセッターは独立したメンバーなので `MemberKind` があります。Spring AOP はメソッドだけを横取りします。
- `proceed` は `Object?` を返し、`Throwable` を宣言しません。

## 関連項目

- [アドバイス](advice.md)
- [ポイントカット](pointcut.md)
- [プロキシ](proxies.md)
