A colleague writes execution(* com.app..*.*(..)) but their advice never fires on some methods. What proxy-related reasons could explain this, given how Spring AOP evaluates execution() pointcuts?
answer
- matching ≠ intercepting
- self-invocation bypasses proxy
- JDK = interface/public only
- CGLIB can't override private/final
- AspectJ LTW for the hard cases
basics
~20 sSpring AOP is proxy-based, so execution() only advises calls that go through the proxy: external calls to Spring-managed beans. Self-invocation (this.method()), private/final methods, and non-bean objects won't be intercepted even if the pointcut technically matches.
solid answer
~50 sexecution() may match a method's signature, yet advice still won't run because Spring AOP intercepts at the proxy, not the bytecode. Reasons: (1) Self-invocation — one method calling another on the same instance uses this, bypassing the proxy, so the inner call is never advised. (2) The object isn't a Spring bean — execution() only applies to beans the container wraps. (3) Non-public methods — with JDK dynamic proxies only interface (public) methods are advised; with CGLIB, private and final methods can't be overridden, so they're never intercepted. (4) The advised method is invoked before the proxy exists (e.g. from a constructor or @PostConstruct on the same bean). The pointcut is necessary but not sufficient — reachability through the proxy is what actually triggers advice. Fixes include AopContext.currentProxy(), self-injection, or switching to load-time/compile-time AspectJ weaving.
code
java · 23 lines@Service
public class OrderService {
// Self-injection so internal calls go THROUGH the proxy
@Autowired
private OrderService self;
public void placeOrder() {
// self.validate() IS advised; validate() (== this.validate()) is NOT
self.validate();
}
@Transactional
public void validate() { /* ... */ }
}
// Aspect whose execution() matches validate() but only fires via the proxy
@Aspect
@Component
class AuditAspect {
@Before("execution(* com.app..OrderService.validate(..))")
public void audit(JoinPoint jp) { /* runs only on proxied calls */ }
}go deeper
Likely unaware of proxy limits; not expected to diagnose this.
Should know self-invocation is a problem for @Transactional/@Cacheable.
Should enumerate proxy reasons (self-call, visibility, non-bean) and know the fixes.
Should weigh proxy vs AspectJ LTW trade-offs and design aspects to avoid self-invocation traps.
## The core insight: matching ≠ intercepting An `execution()` pointcut answers *"does this method's signature match?"* But whether advice actually runs also depends on *"can Spring's proxy intercept this call?"* Spring AOP is **proxy-based**: at startup the container replaces each advised bean with a **proxy** (a JDK dynamic proxy if the bean implements interfaces, otherwise a CGLIB subclass). Advice runs only when a call **passes through** that proxy. ## Reason 1 — Self-invocation (the #1 cause) ```java @Service public class OrderService { public void placeOrder() { validate(); // internal call on 'this' — NOT advised } @Transactional public void validate() { ... } } ``` Inside `placeOrder`, `validate()` is really `this.validate()`. `this` is the **raw bean**, not the proxy, so the proxy never sees the call and no advice fires — even though `execution(* validate(..))` matches. This is also why `@Transactional`/`@Cacheable` silently do nothing on self-invoked methods. **Fixes:** inject the bean into itself and call through the injected reference; use `((OrderService) AopContext.currentProxy()).validate()` with `@EnableAspectJAutoProxy(exposeProxy = true)`; or move the method to a separate bean. ## Reason 2 — Not a Spring bean `execution()` only advises beans the container manages. A `new OrderService()` you construct yourself, or a domain object created by JPA/`new`, is never proxied, so the pointcut is irrelevant. ## Reason 3 — Method visibility / finality - **JDK dynamic proxy** (interface-based): only methods declared on the **interface** — inherently public — are advised. A public method not on any interface won't be intercepted by a JDK proxy. - **CGLIB proxy** (subclass): works by **overriding** methods, so `private`, `final`, and `static` methods can't be overridden and are never advised. Package-private/protected can be advised with CGLIB but Spring conventionally targets public. So `execution(* com.app..*.*(..))` might list a `private` helper, but it will never be intercepted. ## Reason 4 — Call happens before the proxy is in play Calls made from a constructor or `@PostConstruct` of the same bean run on the raw instance during initialization, before the proxy hands out references — advice won't fire. ## Reason 5 — Proxy mode / weaving mismatch Spring AOP evaluates `execution()` at runtime against proxied beans. Full **AspectJ** weaving (load-time or compile-time via `@EnableLoadTimeWeaving` / the AspectJ compiler) instruments the actual bytecode, so it *can* advise self-invocations, private methods, and non-Spring objects. If a candidate expects full AspectJ semantics from plain Spring AOP, that's a misconception. ## How to diagnose 1. Is the target a Spring bean? 2. Is the call external (through an injected reference) or internal (`this`)? 3. Is the method public and non-final? 4. Is proxying JDK or CGLIB (`spring.aop.proxy-target-class`)? If all check out and it still fails, verify the pointcut string itself (single-dot sub-package trap, wrong package). ## When to reach for real AspectJ When you genuinely must advise self-invocations, private methods, constructors, or non-Spring objects, switch to AspectJ load-time weaving; plain proxy-based Spring AOP cannot do it regardless of how you write `execution()`.
- How do you make a self-invoked method get advised without moving it to another bean?Inject the bean into itself (self-injection) and call through that reference, or enable exposeProxy=true and call AopContext.currentProxy(). Both route the call through the proxy.
- Why can't a CGLIB proxy advise a final method?CGLIB proxies work by subclassing the target and overriding methods to insert advice. final methods cannot be overridden, so the proxy can't intercept them.
- What changes if you use AspectJ load-time weaving instead of Spring AOP?Weaving instruments the actual bytecode, so self-invocations, private methods, constructors, and even non-Spring objects can be advised — none of which proxy-based Spring AOP can do.
saying these in an interview costs you the question
- Believing a matching execution() pointcut guarantees advice fires
- Not knowing self-invocation bypasses the proxy
- Thinking Spring AOP can advise private or final methods via CGLIB
- Assuming Spring AOP weaves bytecode like full AspectJ