AOP

AOP (aspect-oriented programming) lets you run your own code before, after or around the methods of other beans, without editing those beans.

Why you need it

Some concerns touch many classes at once: logging, timing, auditing, permission checks, retries. Writing that code inside every method repeats it and mixes it with the real work. With AOP you write it once, in an aspect, and say with a pointcut which methods it applies to.

You do not need AOP for code that belongs to one class only. Put that code in the class. AOP is also not free: an advised bean is replaced by a generated proxy, and calls a bean makes to itself skip the advice (see Proxies).

The words

Term What it is In code
Aspect A bean that holds the extra code a class with @Aspect() and @Component()
Advice One piece of extra code, and when it runs a method with @Before, @After, @AfterReturning, @AfterThrowing or @Around
Pointcut Which methods the advice applies to the string in the annotation, such as '@annotation(Timed)'
Join point One intercepted call the JoinPoint or ProceedingJoinPoint parameter
Proxy The generated wrapper that calls the advice written by the generator, injected instead of the bean

Setup

AOP is part of spring_core_dart. Import package:spring_core_dart/aop.dart, or package:spring_core_dart/spring_core_dart.dart, which exports it. Dart has no runtime reflection, so the proxies are written at build time by spring_core_dart_generator. Your package must depend on it and run build_runner.

dependencies:
  spring_core_dart: ^0.7.1

dev_dependencies:
  build_runner: ^2.17.0
  spring_core_dart_generator: ^0.7.1
dart run build_runner build

A short example

Time every method marked @Timed().

// lib/src/beans.dart
import 'package:spring_core_dart/spring_core_dart.dart';
import 'package:my_app/spring_gen/src/beans.spring.dart'; // written by the generator

// The module: every class in this directory and below is scanned.
@ComponentScan()
final BeanModule appBeans = $appBeans;

// An annotation is a plain class with a const constructor.
class Timed {
  const Timed();
}

// The aspect: a bean whose @Around method runs instead of each
// method marked @Timed().
@Aspect()
@Component()
@Log()
class TimingAspect {
  @Around('@annotation(Timed)')
  Object? time(ProceedingJoinPoint call) {
    final watch = Stopwatch()..start();
    try {
      return call.proceed(); // runs the real method
    } finally {
      log.info('${call.signature.name}: ${watch.elapsedMilliseconds} ms');
    }
  }
}

// An ordinary bean. It does not know about the aspect.
@Service()
class Search {
  @Timed()
  List<String> find(String text) => ['dart', 'spring'];
}
// bin/main.dart
import 'package:my_app/src/beans.dart';
import 'package:spring_core_dart/spring_core_dart.dart';

void main() {
  final context = ApplicationContext.run([appBeans]);
  // getBean returns the proxy, so the advice runs and logs "find: 0 ms".
  context.getBean<Search>().find('d');
  context.close();
}

No extra switch is needed. The context turns AOP on as soon as it finds an @Aspect or an Advisor bean.

Pages in this section

In Spring (Java)

@Configuration
@EnableAspectJAutoProxy
@ComponentScan
class AppConfig {}

@Aspect
@Component
class TimingAspect {
  @Around("@annotation(com.example.Timed)")
  Object time(ProceedingJoinPoint call) throws Throwable {
    long start = System.nanoTime();
    try {
      return call.proceed();
    } finally {
      log.info(call.getSignature().getName() + ": "
          + (System.nanoTime() - start) / 1_000_000 + " ms");
    }
  }
}

Differences that matter:

See also