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
- @Aspect - declare a bean that holds advice; ordering and per-instance aspects.
- @Pointcut and pointcut expressions - name a pointcut and the expression syntax that selects methods.
- Advice -
@Before,@After,@AfterReturning,@AfterThrowingand@Around. - @DeclareParents - make the proxies of matching beans implement an extra interface.
- JoinPoint - what advice can read about the intercepted call, and
MethodInterceptor. - Proxies - how proxies are made, self-calls, advisors in code, and the limits.
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:
- Spring creates proxies at runtime (JDK dynamic proxies or CGLIB). Here the generator writes the proxy classes at build time, so
build_runnermust run after every change. - There is no
@EnableAspectJAutoProxy. An aspect or advisor bean is enough. - Type names in pointcuts are simple names (
Timed, notcom.example.Timed). Dart has no packages. - Dart getters and setters, including the implicit ones of public fields, can be advised too.