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
// 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. |
// 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.
// 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.
// 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().
// 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)
@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, notgetSignature(),getArgs(). MethodSignatureholds type names as strings, notClassobjects or ajava.lang.reflect.Method.MemberKindexists because Dart getters and setters are separate members. Spring AOP intercepts methods only.proceedreturnsObject?and declares noThrowable.