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

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:

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

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:

See also