@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
*in a name matches any characters:find*,*Service,*.- In
executionparameters,..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 thefind*methods of every bean. Type+inwithinandexecutionmeans the type and its subtypes:execution(* Repository+.*(..)).- Type arguments and
?are ignored:Future<int>matchesFuture,int?matchesint. - Type names are simple names.
com.x.ServicemeansService. A package wildcard such aswithin(com.x..*)is an error, so it does not silently match everything. executionmodifiers:public;protected, which matches members annotated@protectedfrompackage:meta; andprivate, which never matches, because private members are not proxied. Each can be negated with!.- A
throwsclause is an error. Dart methods declare no exceptions; filter errors with a typed@AfterThrowingparameter 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.
// 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:
- 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,ifand the other AspectJ designators are not. executionhas nothrowsclause;protectedmeans@protectedfrompackage:meta.- Getters and setters are their own members in Dart.
executionnever matches them, unlike Javaget*methods. - Invalid pointcuts fail the build, not the startup.