What is Spring AOP, and how does it use proxies (JDK dynamic proxies vs CGLIB)?

9 minadvancedaopproxyaspect

Quick Answer

Spring AOP (Aspect-Oriented Programming) lets you apply cross-cutting behavior (logging, transactions, security checks) around method calls, declared separately from the business logic itself via @Aspect classes and pointcut expressions. It's implemented via runtime proxies: if the target bean implements an interface, Spring uses a JDK dynamic proxy (implementing that same interface); otherwise it uses a CGLIB proxy (a runtime-generated subclass of the target class) — this is also why @Transactional and similar annotations don't take effect on private methods or on self-invoked calls within the same class.

Detailed Answer

AOP (Aspect-Oriented Programming) lets you factor out cross-cutting concerns — logging, transaction management, security checks, caching — that would otherwise be scattered as repetitive boilerplate across many unrelated methods, and instead declare them once, separately, as an aspect:

@Aspect
@Component
class LoggingAspect {
    @Around("execution(* com.example.service.*.*(..))") // pointcut: which methods this applies to
    Object logExecutionTime(ProceedingJoinPoint pjp) throws Throwable {
        long start = System.currentTimeMillis();
        Object result = pjp.proceed(); // actually invoke the real method
        System.out.println(pjp.getSignature() + " took " + (System.currentTimeMillis() - start) + "ms");
        return result;
    }
}

Many familiar Spring annotations (@Transactional, @Async, @Cacheable, @PreAuthorize) are themselves implemented as built-in aspects under the hood.

How it's implemented — proxies: Spring AOP (unlike full AspectJ compile-time/load-time weaving) works by wrapping the target bean in a runtime proxy that intercepts method calls, runs the relevant aspect logic, and then delegates to the real object:

  • JDK dynamic proxy: used when the target bean implements at least one interface — Spring generates a proxy implementing that same interface at runtime, delegating calls through the aspect logic to the real implementation.
  • CGLIB proxy: used when the target bean has no interface (or Spring is configured to always prefer class-based proxying) — Spring generates a runtime subclass of the target class itself, overriding its public/protected methods to insert the aspect logic.

Why this matters practically — two classic gotchas:

  1. Self-invocation bypasses the proxy: calling another @Transactional/aspect-advised method on this, from within the same class, calls the real method directly — never going through the proxy — so the aspect (transaction, logging, etc.) simply doesn't apply. This is why calling one @Transactional method from another method in the same class famously doesn't get its own transaction.
  2. final methods/classes can't be proxied by CGLIB (it needs to generate a subclass, which can't override final methods), and private methods can never be advised by either proxy style, since a proxy can only intercept calls that actually go through it externally.