skip to content

Explain the proxy mechanism that causes self-invocation to bypass advice. How do JDK vs CGLIB proxies fit in?

level: middleimportance: should knowfreq 60%

answer

  1. proxy = wrapper sharing bean's type
  2. JDK = interface proxy; CGLIB = subclass
  3. interception on proxy, not target body
  4. this = raw target, never proxy
  5. CGLIB can't touch final/private

basics

~20 s

Spring wraps your bean in a proxy object that runs advice before delegating to the real target. Other beans get the proxy; but inside the bean, this is the raw target, so its own calls skip the proxy and its advice.

solid answer

~40 s

Proxy-based AOP works by giving collaborators a wrapper instead of your bean. The wrapper is either a JDK dynamic proxy (an InvocationHandler behind the bean's interfaces) or, by default for classes, a CGLIB subclass that overrides methods. On each proxied method the proxy runs the interceptor chain (TransactionInterceptor, CacheInterceptor, custom advice) and then calls the real target. Because interception happens on the proxy, only calls arriving through the proxy reference are advised. Inside the target, this points at the unwrapped instance, so this.method() dispatches straight to target code and no interceptor runs. JDK vs CGLIB doesn't change this — both are caller-side wrappers. CGLIB additionally can't advise final or private methods since it works by subclassing. Spring Boot defaults to CGLIB (proxyTargetClass=true) even when interfaces exist.

code

java · 13 lines
java
// Spring Boot default: proxyTargetClass=true -> CGLIB subclass proxy
// Conceptually the generated proxy looks like:
class OrderService$$SpringCGLIB extends OrderService {
    private final Interceptor chain; // TransactionInterceptor, etc.

    @Override
    public void save(Order o) {
        chain.invoke(() -> super.save(o)); // advice runs here
    }
}
// External bean holds OrderService$$SpringCGLIB -> save() is advised.
// Inside OrderService.placeOrder(), 'this' is the plain OrderService,
// so this.save(o) calls the un-overridden method -> chain skipped.

go deeper

for a junior

Knows a proxy wraps the bean and internal calls skip it; may not distinguish JDK vs CGLIB.

for a middle

Explains both proxy types, where interception lives, and that self-invocation escapes regardless of type.

for a senior

Connects the mechanism to concrete fixes and to CGLIB's final/private limitations.

for a principal

Reasons about proxy creation lifecycle, Boot's proxyTargetClass default, and why caller-side interception is inherent to proxying.

## What a proxy is An **AOP proxy** is an object Spring creates that stands in front of your bean and shares its type. When you `@Autowired` a bean that needs advice, the container injects the **proxy**, not the raw instance. The proxy's job is to run the **advice** (the cross-cutting behavior) and then forward to the **target** (your actual object). ## Two proxy strategies 1. **JDK dynamic proxy** — used when the bean implements at least one interface (and `proxyTargetClass` is false). Java's `java.lang.reflect.Proxy` generates a class implementing those interfaces; every method routes into an `InvocationHandler` that runs the interceptor chain. Only interface-declared methods can be advised. 2. **CGLIB proxy** — used for classes without interfaces, or whenever `proxyTargetClass=true`. CGLIB generates a **runtime subclass** of your bean and overrides its methods to insert the interceptor chain. Because it subclasses, it **cannot override `final` classes/methods or `private` methods**, so those can't be advised. **Spring Boot enables CGLIB by default** (`spring.aop.proxy-target-class=true`). Either way, the interception logic lives **on the proxy/subclass wrapper**, not in your original method bodies. ## The interceptor chain When a call reaches the proxy, Spring runs an ordered chain of `MethodInterceptor`s: e.g. `TransactionInterceptor` starts/commits a transaction, `CacheInterceptor` handles `@Cacheable`, `AsyncExecutionInterceptor` handles `@Async`, plus any `@Aspect` advice. Each wraps the next; the last delegates to the real target method. ## Why self-invocation escapes it The crucial detail: **inside the target object, `this` is the raw target, never the proxy.** Spring wires the target's own fields to the target instance. So when your method calls `this.other()` (or just `other()`), the JVM dispatches directly on the target — the proxy subclass override is not involved, and the interceptor chain never runs. The advice is bypassed. This is why an internal call to a `@Transactional`/`@Cacheable` method silently does nothing. JDK vs CGLIB makes **no difference** here — both are caller-side wrappers, and neither can intercept a call the target makes to itself. ## Gotchas - CGLIB can't advise `final`/`private` methods — a distinct but frequently-conflated limitation. - With JDK proxies, casting the injected bean to the concrete class fails (`ClassCastException`) because the proxy only implements the interfaces. - `proxyTargetClass=true` forces CGLIB even when interfaces are present. - The proxy is created by `AbstractAutoProxyCreator` post-processors during bean initialization. ## When it matters Understanding this tells you the fix is to make the call **cross the proxy boundary** (separate bean, self-injection, `AopContext.currentProxy()`), or to abandon proxies for **AspectJ weaving** where advice is compiled into the method body itself.

  • Does Spring Boot use JDK or CGLIB proxies by default?
    CGLIB — Spring Boot sets proxyTargetClass=true, so class-based proxies are created even when the bean implements interfaces.
  • Why can't CGLIB advise a private method?
    CGLIB proxies by generating a subclass and overriding methods; private (and final) methods can't be overridden, so no interceptor can be inserted.

saying these in an interview costs you the question

  • Claiming JDK vs CGLIB choice fixes self-invocation
  • Saying the proxy modifies the original bytecode of your method
  • Thinking interfaces are required for Spring AOP (CGLIB needs none)
  • Believing the target holds a reference to its own proxy by default

context