Spring AOP Proxies
Spring AOP (Aspect-Oriented Programming) applies cross-cutting concerns (logging, transactions, security) via proxies. Spring AOP is proxy-based (not full AspectJ bytecode weaving) and only works for method execution join points on Spring beans.
AOP Proxy = a stunt double who intercepts all incoming calls and adds special effects (logging, transactions) before passing to the real actor. Self-calls skip the stunt double entirely -- the actor talks to themselves, no special effects.
Two proxy types: JDK dynamic proxy (requires interface — proxy implements the same interface) and CGLIB proxy (no interface required — subclasses the target class, so the class cannot be final). Spring Boot defaults to CGLIB proxies. Advice types: @Before, @After, @AfterReturning, @AfterThrowing, @Around. @Around is the most powerful — controls whether the real method is called at all. Pointcut expressions target methods by signature. AOP applies at runtime (not compile time) — no bytecode modification, just object wrapping.
Spring AOP is a subset of AspectJ. Full AspectJ (load-time or compile-time weaving) can intercept field access, static methods, and constructor calls — Spring AOP cannot. The proxy is created by AbstractAutoProxyCreator (a BeanPostProcessor) after the bean is instantiated. Proxies can be stacked — @Transactional and @Async and custom security advice can all wrap the same bean. Ordering (@Order / Ordered) determines the advice execution sequence. A common pitfall: casting a Spring bean to a concrete class fails when CGLIB is used and the class has a final method — CGLIB cannot override it, causing proxy creation failure or silent advice bypass.
Spring AOP wraps beans in JDK dynamic proxies (interface-based) or CGLIB proxies (subclass-based) at runtime. Interception only works for calls that go through the proxy — calls from outside the bean. Internal self-calls skip the proxy. CGLIB cannot proxy final classes or override final methods — those escape all AOP advice. @Around advice is the most powerful: it controls invocation via ProceedingJoinPoint.proceed() and can modify arguments, return values, or suppress the call entirely.
If a Spring bean's class is final (e.g., a Kotlin data class by default), CGLIB cannot subclass it — Spring fails to create the proxy and the @Transactional/@Async/@Cacheable annotations on it are silently ignored. Mark Kotlin classes open or use the allopen plugin.