@Aspect
@Aspect() marks a bean class whose methods are advice: code that runs before, after or around the calls of other beans.
Why you need it
An aspect is the place where cross-cutting code lives: logging, timing, auditing, checks. You write it once, and its pointcuts decide which methods of which beans it applies to. The advised beans stay unchanged.
You do not need an aspect when:
- the extra code concerns one class only: put it in that class;
- the advice is a reusable object chosen in code, for example in a library: declare a
PointcutAdvisorbean instead (see Proxies).
Declaring an aspect
The aspect must also be a bean. Add @Component(), or return it from a @Bean method. It can inject other beans like any bean. It is never proxied itself.
import 'package:spring_core_dart/spring_core_dart.dart';
// A plain bean that stores audit lines.
@Component()
class AuditTrail {
final lines = <String>[];
void record(String line) => lines.add(line);
}
// Records every call to the order service.
@Aspect()
@Component()
class AuditAspect {
AuditAspect(this.trail); // aspects inject other beans as usual
final AuditTrail trail;
@Before('execution(* OrderService.*(..))')
void audit(JoinPoint joinPoint) =>
trail.record('${joinPoint.toShortString()} ${joinPoint.args}');
}
The methods of an aspect carry one advice annotation each: @Before, @After, @AfterReturning, @AfterThrowing or @Around (see Advice). Empty methods with @Pointcut give a pointcut a name (see Pointcuts). Advice and @Pointcut methods of the aspect's superclasses and mixins are inherited.
Ordering several aspects
When several aspects advise the same call, @Order on the aspect class decides, or implementing Ordered. The lower value is the outer one: its @Before runs first and its @After runs last. Aspects without an order come last.
// Security checks run outside timing, so a refused call is not timed.
@Aspect()
@Component()
@Order(1)
class SecurityAspect {
@Before('@within(Service)')
void check(JoinPoint joinPoint) {
// throw to refuse the call
}
}
@Aspect()
@Component()
@Order(2)
@Log()
class TimingAspect {
@Around('@within(Service)')
Object? time(ProceedingJoinPoint call) {
final watch = Stopwatch()..start();
try {
return call.proceed();
} finally {
log.info('${call.toShortString()} ${watch.elapsedMilliseconds} ms');
}
}
}
Inside one aspect, advice nests by kind: around, then before, after, afterReturning, afterThrowing. Advice of the same kind keeps declaration order.
Per-instance aspects
By default there is one aspect instance for the whole application. The annotation's value can change this:
perthis(pointcut): one aspect instance per proxy;pertarget(pointcut): one aspect instance per target bean.
Use this when the aspect keeps state for each advised object. Such an aspect must be a prototype bean (@Scope('prototype')). Its advice applies only where both the per-clause pointcut and the advice's own pointcut match.
// Counts the calls of each Orders proxy separately.
@Aspect('perthis(execution(* Orders.*(..)))')
@Component()
@Scope('prototype')
class CallCounter {
int calls = 0;
@Before('execution(* Orders.*(..))')
void count() => calls++;
}
The build fails for a singleton per-instance aspect, for percflow and for pertypewithin.
Parameters
| Name | Type | Default | Meaning |
|---|---|---|---|
value |
String |
'' |
The instantiation model. Empty: one shared instance. perthis(pointcut): one per proxy. pertarget(pointcut): one per target bean. |
Rules
@Aspect()alone does not make a bean. Add@Component()or a@Beanmethod.- The generator must run (
dart run build_runner build). It checks every pointcut of the aspect and fails the build on a syntax error, with the aspect, the advice and the position. - Aspects of a parent context also apply to the beans of its child contexts.
- An aspect turned off by a condition or a profile is simply not registered, and its advice does not apply.
In Spring (Java)
@Aspect
@Component
@Order(1)
class AuditAspect {
@Before("execution(* com.example.OrderService.*(..))")
void audit(JoinPoint joinPoint) {
log.info(joinPoint.toShortString());
}
}
@Aspect("perthis(execution(* com.example.Orders.*(..)))")
@Component
@Scope("prototype")
class CallCounter { ... }
Differences that matter:
- No
@EnableAspectJAutoProxy: the context registers the auto-proxy creator itself when an aspect or advisor bean exists. percflowandpertypewithinare not supported.- Pointcuts use simple type names, not package-qualified ones.