
# @Pointcut and pointcut expressions

A pointcut is an expression that says which members of which beans an advice applies to; `@Pointcut` gives such an expression a name.

## Why you need it

Every advice annotation takes a pointcut: `@Before('execution(* OrderService.*(..))')`. The expression selects methods by name, type, annotation or bean name. When several advice methods use the same expression, write it once with `@Pointcut` and refer to it by name.

You do not need `@Pointcut` when an expression is used only once. Write it directly in the advice annotation.

## Naming a pointcut

Put `@Pointcut` on an empty method of an aspect. The method body is never called. Other pointcuts of the same aspect refer to it as `name()`. Pointcuts in another aspect write `AspectClass.name()`.

```dart
@Aspect()
@Component()
@Log()
class AuditAspect {
  // Every OrderService member, named once.
  @Pointcut('within(OrderService)')
  void orders() {}

  @Before('orders()')
  void before(JoinPoint call) => log.info('calling ${call.toShortString()}');

  // Combine a named pointcut with other designators.
  @After('orders() && !execution(* *.slow*(..))')
  void after() => log.info('done');
}

@Aspect()
@Component()
@Log()
class MetricsAspect {
  // AuditAspect's pointcut, reused here with the class name.
  @After('AuditAspect.orders()')
  void count() => log.info('order call');
}
```

Named pointcuts of the aspect's superclasses and mixins are inherited.

| Name | Type | Default | Meaning |
|---|---|---|---|
| `expression` | `String` | required | The pointcut expression the method name stands for. |

## Expression syntax

A pointcut is one or more designators combined with operators:

```text
designator(pattern)  [&& | || | !  designator(pattern) ...]
```

Operators: `&&` or `and`, `||` or `or`, `!` or `not`, and parentheses.

### Designators

| Designator | Matches | Example |
|---|---|---|
| `execution([modifiers] ret [Type.]name(params))` | methods by return type, declaring type, name and parameters | `execution(* OrderService.find*(..))` |
| `within(TypePattern)` | every member whose body is in a matching class. An inherited member that is not overridden is within its superclass. `Type+` also matches subtypes | `within(*Repository)`, `within(Repository+)` |
| `target(Type)`, `this(Type)` | every member of beans of that type or a subtype (interfaces too) | `target(PaymentGateway)` |
| `args(types)` | methods whose arguments have these types: the declared parameter type, else the runtime type of the argument (`..` for any number) | `args(String, ..)` |
| `@args(Annotation, ..)` | methods whose arguments' classes carry the annotations | `@args(Audited)` |
| `bean(namePattern)` | every member of beans with that name, an alias or a qualifier value | `bean(order*)` |
| `@annotation(Annotation)` | methods carrying the annotation, also when it is on the interface method they implement | `@annotation(Timed)` |
| `@within(Annotation)` | every member whose body is in a class carrying the annotation | `@within(Service)` |
| `@target(Annotation)` | every member of beans whose class carries the annotation | `@target(Service)` |
| `name()`, `Type.name()` | a named pointcut of the same aspect, or of the class `Type` | `orders()`, `AuditAspect.orders()` |

Any other designator (`cflow`, `if`, ...) fails with "unknown designator".

### Patterns

- `*` in a name matches any characters: `find*`, `*Service`, `*`.
- In `execution` parameters, `..` matches any number of parameters (including none) and `*` exactly one: `(..)`, `()`, `(String, ..)`, `(*, int)`.
- The return type comes first; `*` means any: `execution(* *(..))`, `execution(void *(..))`, `execution(Future *(..))`.
- The type before the method name is optional: `execution(* find*(..))` matches the `find*` methods of every bean.
- `Type+` in `within` and `execution` means the type and its subtypes: `execution(* Repository+.*(..))`.
- Type arguments and `?` are ignored: `Future<int>` matches `Future`, `int?` matches `int`.
- Type names are simple names. `com.x.Service` means `Service`. A package wildcard such as `within(com.x..*)` is an error, so it does not silently match everything.
- `execution` modifiers: `public`; `protected`, which matches members annotated `@protected` from `package:meta`; and `private`, which never matches, because private members are not proxied. Each can be negated with `!`.
- A `throws` clause is an error. Dart methods declare no exceptions; filter errors with a typed `@AfterThrowing` parameter instead.

### What `execution` does not match

`execution` matches methods only. It never matches getters or setters, explicit ones or the implicit ones of a public field. Use `within`, `bean`, `target`, `@within`, `@target` or `@annotation` to reach them.

`@annotation(X)` does not match when only a superclass method that the member overrides carries `X`. This follows AspectJ.

### Examples

| Pointcut | Advises |
|---|---|
| `execution(* *(..))` | every method of every bean |
| `within(OrderService)` | everything of `OrderService`, getters and setters included |
| `execution(* OrderService.*(..))` | the methods of `OrderService` |
| `execution(* find*(..))` | methods whose name starts with `find`, in any bean |
| `execution(Future *(..))` | methods that return a `Future` |
| `execution(void *(String, ..))` | `void` methods whose first parameter is a `String` |
| `target(PaymentGateway)` | every bean implementing `PaymentGateway` |
| `bean(order*)` | beans whose name starts with `order` |
| `@annotation(Timed)` | methods marked `@Timed()` |
| `@within(Service)` | every member of `@Service` classes |
| `within(*Service) && !execution(* get*(..))` | services, except `get...` methods |
| `orders() \|\| bean(payments)` | a named pointcut of this aspect, or the `payments` bean |

## Binding values to advice parameters

A lowercase identifier in `@annotation(x)`, `@within(x)`, `@target(x)`, `@args(x)`, `this(x)`, `target(x)` or `args(x, ..)` names a parameter of the advice method. The parameter then receives the annotation object, the proxy, the bean or the argument.

A name that starts with an uppercase letter is a type. `int`, `double`, `num`, `bool`, `dynamic` and `void` are types too.

```dart
// Receives the @Timed annotation object and the first argument.
@Around('@annotation(timed) && args(isbn, ..)')
Object? time(ProceedingJoinPoint joinPoint, Timed timed, String isbn) {
  log.info('timing ${joinPoint.signature.name} for $isbn');
  return joinPoint.proceed();
}
```

The bound value must have the parameter's type, or the call does not match. A pointcut with bindings or argument tests is checked again on every call; the others are decided once, at startup.

A parameter that the pointcut does not bind, or a bound name without a parameter, fails the build.

## Pointcuts in code

Outside aspects, `ExpressionPointcut` takes the same syntax. Use it in a `DefaultPointcutAdvisor` bean. An invalid expression throws a `FormatException`.

```dart
// LoggingInterceptor is your own MethodInterceptor (see JoinPoint).
@Configuration()
class AopConfig {
  @Bean()
  PointcutAdvisor serviceLogging() => DefaultPointcutAdvisor(
    ExpressionPointcut(
      'service() && !execution(* *.slow*(..))',
      // Named pointcuts are passed as a map here.
      namedPointcuts: {'service': 'within(*Service) || @within(Service)'},
    ),
    LoggingInterceptor(),
  );
}
```

Other ready-made pointcuts, all implementing `AopPointcut`:

| Class | Matches |
|---|---|
| `TruePointcut()` | every member of every bean |
| `NameMatchMethodPointcut(names)` | members whose name matches one of the `*` patterns, getters and setters too |
| `AnnotationMatchingPointcut<A>({onClass = false})` | members annotated with `A`, or with `onClass: true` every member of a class annotated with `A` |
| `ComposablePointcut([pointcut])` | a builder: `union(other)` adds "or", `intersection(other)` adds "and" |
| `ExpressionPointcut(expression, {namedPointcuts, ...})` | the expression syntax above |

Implement `AopPointcut` yourself when a rule is easier in Dart. Its one method, `bool matches(MethodSignature signature, String beanName)`, runs once per member of each proxied bean at startup, not on every call.

```dart
// Advises the read-only members: find* methods and getters.
final class ReadOnlyPointcut implements AopPointcut {
  const ReadOnlyPointcut();

  @override
  bool matches(MethodSignature signature, String beanName) =>
      signature.name.startsWith('find') ||
      signature.kind == MemberKind.getter;
}
```

## In Spring (Java)

```java
@Aspect
@Component
class AuditAspect {
  @Pointcut("within(com.example.OrderService)")
  void orders() {}

  @Before("orders()")
  void before(JoinPoint call) { ... }

  @Before("@annotation(timed) && args(isbn, ..)")
  void timed(Timed timed, String isbn) { ... }
}
```

Differences that matter:

- Type names are simple names. `com.x..*` package wildcards are rejected.
- Supported designators: `execution`, `within`, `this`, `target`, `args`, `@args`, `bean`, `@annotation`, `@within`, `@target`, and named pointcuts. `cflow`, `cflowbelow`, `if` and the other AspectJ designators are not.
- `execution` has no `throws` clause; `protected` means `@protected` from `package:meta`.
- Getters and setters are their own members in Dart. `execution` never matches them, unlike Java `get*` methods.
- Invalid pointcuts fail the build, not the startup.

## See also

- [Advice](advice.md)
- [@Aspect](aspect.md)
- [JoinPoint](join-point.md)
- [Proxies](proxies.md)
