Aspect-Oriented Programming
Some concerns — logging, timing, security checks, transactions, caching — show up in every method but don't belong inside business logic.
Introduction
Some concerns — logging, timing, security checks, transactions, caching — show up in every method but don't belong inside business logic. AOP lets you write that behaviour once as an aspect and weave it around the methods you choose. Spring AOP is the most-used real-world example outside the Java standard library.
Understanding AOP is the moment Spring stops feeling magical. @Transactional, @Async, @Cacheable and method-level security are all aspects — once you see how they work, you can build your own.
Understanding the topic
Vocabulary you need:
- Join point — a place where behaviour can be added (a method call, in Spring AOP).
- Pointcut — an expression that selects join points.
- Advice — the code that runs at a join point (
before,after,afterReturning,afterThrowing,around). - Aspect — a class that bundles pointcuts and advice together.
- Weaving — the process of inserting advice; Spring does it at runtime using proxies.
Spring AOP creates a proxy around each advised bean. Calls from outside the bean go through the proxy and hit the advice; calls from inside the bean (via this) skip the proxy. This is the single most important rule to remember.
Syntax reference
An aspect that times every service method:
@Aspect@Componentpublic class TimingAspect {private static final Logger log = LoggerFactory.getLogger(TimingAspect.class);@Around("within(com.acme..*Service)")public Object time(ProceedingJoinPoint pjp) throws Throwable {long start = System.nanoTime();try {return pjp.proceed();} finally {long ms = (System.nanoTime() - start) / 1_000_000;log.info("{} took {} ms", pjp.getSignature(), ms);}}}
Informative example
A custom annotation + aspect — the same pattern Spring uses for @Transactional:
@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)public @interface Audited { String value(); }@Aspect@Componentclass AuditAspect {private final AuditLog log;AuditAspect(AuditLog log) { this.log = log; }@AfterReturning("@annotation(audited)")public void record(JoinPoint jp, Audited audited) {log.write(audited.value(), jp.getSignature().getName(), jp.getArgs());}}@Serviceclass OrderService {@Audited("order.placed")public void place(Order o) { /* ... */ }}
Real-world use
Spring's own @Transactional, @Async, @Cacheable and method-level security are all implemented as aspects. Knowing AOP also explains why mocking a self-call in a unit test won't trigger transactions — the proxy is bypassed.
Best practices
- Keep pointcut expressions narrow; broad
execution(* *(..))patterns slow startup and surprise teammates. - Prefer custom annotations over package-based pointcuts — the intent travels with the code.
- Use
@Aroundonly when you must (e.g. to time or short-circuit);@Before/@Afterare simpler.
Common mistakes
- Spring AOP only intercepts bean method calls from outside the bean. A method calling another method on
thisskips the proxy. - Putting AOP-relevant annotations on
privateorfinalmethods — proxies cannot override them. - Forgetting to enable
@EnableAspectJAutoProxyin a non-Boot setup.
Hands-on exercise
Build it: create an @Audited annotation and an aspect that prints the method name and arguments whenever an audited method runs. Apply it to a couple of service methods. Then write a test that calls one audited method from inside another method of the same class — confirm the inner call is not audited, and explain why.