@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().

@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:

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

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.

// 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.

// 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.

// 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)

@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:

See also