Aspect-Oriented Programming
Factor out cross-cutting concerns like logging, security, and transactions with aspects, advice, and Spring's proxies.
On this page
Some concerns don't belong to any single class - they cut across many of them. Logging, security checks, performance timing, and transaction management need to happen in dozens of methods, yet they have nothing to do with those methods' actual jobs. Scatter that code everywhere and you get noise, duplication, and drift.
Aspect-Oriented Programming (AOP) lets you write such a concern once and have Spring apply it across many methods automatically.
Think of a building's fire-sprinkler system. Fire safety isn't the job of the kitchen, the office, or the lobby - yet every room needs it. You don't install a bespoke sprinkler inside each desk; you run one system across the whole building. AOP is that cross-cutting system for your code: define the concern once, and it covers every method you point it at.
The vocabulary
Four terms unlock every AOP discussion:
- Aspect - the module holding a cross-cutting concern (e.g. "timing").
- Advice - the code to run (before, after, or around a method).
- Join point - a place where advice could apply (a method call).
- Pointcut - an expression selecting which join points to advise.
An aspect in action
Here's a timing aspect that logs how long selected methods take - without touching a single one of them:
@Aspect
@Component
class TimingAspect {
// pointcut: any method in the service package
@Around("execution(* com.bookvault.service..*(..))")
Object time(ProceedingJoinPoint call) throws Throwable {
long start = System.nanoTime();
try {
return call.proceed(); // run the real method
} finally {
long ms = (System.nanoTime() - start) / 1_000_000;
log.info("{} took {} ms", call.getSignature(), ms);
}
}
}Every method under com.bookvault.service is now timed. Add a new service tomorrow
and it's timed too - zero extra code. The service classes stay blissfully unaware.
How Spring does it: proxies
Remember from the container lesson that because Spring hands out your beans, it can hand out enhanced ones. That's exactly what happens here. Spring wraps the target bean in a proxy - a stand-in object with the same interface. Callers get the proxy; it runs the advice, then delegates to the real bean.
Nothing exists yet — you've only written the classes and their dependencies.
Proxies only intercept external calls
Because the proxy sits around the bean, advice runs only when a call comes in from
outside. If a bean calls its own method internally (this.otherMethod()), the
call skips the proxy - so the advice (and @Transactional!) won't fire. This
"self-invocation" gotcha catches everyone once.
The AOP you'll use constantly
You'll rarely write aspects day to day - but you'll use them everywhere, because Spring's most important features are built on AOP:
@Transactionalwraps a method in a database transaction (Data module).@Cacheablecaches a method's result.@Asyncruns a method on another thread.- Method security (
@PreAuthorize) checks permissions before a method runs.
Each is "advice" applied by a proxy - the same mechanism as the timing aspect. Once you see AOP, you see it all over Spring.
A teammate reports that @Transactional "isn't working": a method annotated with it
runs, but no transaction seems to start. On inspection, that method is being called
by another method in the same class. Using what you learned about proxies, explain
why the transaction never begins - and suggest a fix.
How does Spring apply an aspect's advice to a bean's methods?
Key takeaways
- AOP factors out cross-cutting concerns (logging, timing, security, transactions) so you write them once instead of scattering them across many methods.
- Key terms: aspect (the concern), advice (the code), join point (where it could run), pointcut (which join points to select).
- Spring implements AOP with proxies - it hands out a wrapped bean that runs advice then delegates to the real object.
- Self-invocation (this.method()) bypasses the proxy, so advice and @Transactional won't fire on internal calls.
- You'll mostly consume AOP through @Transactional, @Cacheable, @Async, and method security rather than writing aspects yourself.