
# JoinPoint

A `JoinPoint` describes one intercepted call: which member is called, on which bean, with which arguments.

## Why you need it

Advice often needs to know what it is advising: to write a log line, to name a metric after the method, or to read the method's annotations. Declare a `JoinPoint` parameter and the proxy passes it in. `@Around` advice takes a `ProceedingJoinPoint`, which can also run the call.

You do not need it when the advice does the same thing for every call, such as `@After('orders()') void after() => ...`. The parameter is optional for every advice except `@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}');
```

| Member | Type | Meaning |
|---|---|---|
| `proxy` | `Object` | The proxy the caller called, the object other beans hold. For a generated subclass proxy it is the bean itself. |
| `target` | `Object` | The bean whose member runs: the object behind the proxy. |
| `signature` | `MethodSignature` | The called member: its name, types and annotations. |
| `args` | `List<Object?>` | The arguments: positional ones, then named ones. A copy: changing it does not change the call. |
| `toShortString()` | `String` | A short description for logs, such as `execution(Greeter.greet(..))`. |

## ProceedingJoinPoint

The join point of `@Around` advice. It adds one method:

| Member | Type | Meaning |
|---|---|---|
| `proceed([List<Object?>? args])` | `Object?` | Runs the next advice, or the member itself after the last one, and returns its result. `args` replaces the arguments and must have as many elements as the call has, else an `ArgumentError` is thrown. For a member returning a `Future`, the result is that 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

What is known about the advised member without calling it. Dart has no runtime reflection, so the generator writes one constant per proxied member. Type names are simple class names without type arguments (`Future`, not `Future<int>`). They are written at build time, so they also work in minified and obfuscated builds. Applications read it; they never create it.

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

| Member | Type | Meaning |
|---|---|---|
| `name` | `String` | The member's name, such as `add`. A setter's name has no `=`. |
| `kind` | `MemberKind` | `method`, `getter` or `setter`. |
| `declaringType` | `String` | The most abstract type that declares the member, such as the interface `Calculator`. |
| `declaringTypes` | `List<String>` | Every type of the bean that declares the member. |
| `targetType` | `String` | The bean's class, such as `SimpleCalculator`. |
| `targetTypes` | `List<String>` | The bean class and all its supertypes. |
| `returnType` | `String` | The return type's name; `void` when it returns nothing, `dynamic` when unknown. |
| `parameterTypes` | `List<String>` | The parameter types' names. |
| `parameterNames` | `List<String>` | The parameter names, in the order of `args`. |
| `parameterAnnotations` | `List<List<Object>>` | The annotations of each parameter. |
| `annotations` | `List<Object>` | The member's annotations that `@annotation(...)` matches: those of the method that runs and of the interface methods it implements. |
| `mergedAnnotations` | `List<Object>` | The annotations of the member and of every declaration it overrides or implements, most specific first. |
| `targetAnnotations` | `List<Object>` | The annotations on the bean class. |
| `implementingType` | `String` | The class whose body runs for the member: the bean class, or the superclass or mixin it inherits it from. |
| `isProtected` | `bool` | Whether the member is annotated `@protected`. |
| `toShortString()` | `String` | Such as `Greeter.greet(..)`. |

## MemberKind

Dart has getters and setters as members of their own, and a public field is an implicit getter (and setter, unless `final`). Proxies advise them like methods. Read `signature.kind` when advice must treat property access differently.

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

| Value | Meaning |
|---|---|
| `MemberKind.method` | A method; the only kind `execution(...)` matches. |
| `MemberKind.getter` | A getter, explicit or the implicit one of a public field. |
| `MemberKind.setter` | A setter, explicit or the implicit one of a non-final public field. |

## MethodInterceptor and MethodInvocation

Advice can also be a class instead of an aspect method. Implement `MethodInterceptor` and pair it with a pointcut in a `DefaultPointcutAdvisor` bean. Use this for reusable advice, such as a library's cache or security check.

Its `invoke` method receives a `MethodInvocation`: a `ProceedingJoinPoint` whose `args` are the live arguments. An interceptor may change them in place before it calls `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());
}
```

| Member | Type | Meaning |
|---|---|---|
| `MethodInterceptor.invoke(MethodInvocation invocation)` | `Object?` | Runs around the call and returns its result. Call `invocation.proceed()` to continue; not calling it skips the member. |
| `DefaultPointcutAdvisor(pointcut, advice, {order})` | - | A pointcut, an interceptor and an order. `order` defaults to `Ordered.lowestPrecedence` (the innermost). Since it implements `Ordered`, `@Order` on its `@Bean` method is ignored. |

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

Differences that matter:

- Properties instead of getters: `joinPoint.signature`, `joinPoint.args`, not `getSignature()`, `getArgs()`.
- `MethodSignature` holds type names as strings, not `Class` objects or a `java.lang.reflect.Method`.
- `MemberKind` exists because Dart getters and setters are separate members. Spring AOP intercepts methods only.
- `proceed` returns `Object?` and declares no `Throwable`.

## See also

- [Advice](advice.md)
- [Pointcuts](pointcut.md)
- [Proxies](proxies.md)
