skip to content

Explain the proxyTargetClass and exposeProxy attributes of @EnableAspectJAutoProxy and when you'd set each.

level: seniorimportance: should knowfreq 40%

answer

  1. proxyTargetClass=true -> always CGLIB
  2. JDK proxy = interfaces only
  3. CGLIB can't proxy final class/method
  4. exposeProxy -> AopContext.currentProxy()
  5. Fixes self-invocation bypass

basics

~10 s

proxyTargetClass=true forces CGLIB class proxies instead of JDK interface proxies. exposeProxy=true publishes the current proxy via AopContext.currentProxy() so a bean can call its own advised methods through the proxy instead of bypassing it.

solid answer

~40 s

proxyTargetClass controls the proxy strategy. Default false: if the target implements at least one interface, Spring makes a JDK dynamic proxy (only the interfaces are advisable); otherwise it falls back to CGLIB. Set true to always use CGLIB (a runtime subclass), which is needed when callers depend on the concrete type or non-interface methods — Spring Boot defaults this to true. exposeProxy addresses self-invocation: when a bean calls this.someAdvisedMethod(), the call goes straight to the target and skips the proxy, so advice like @Transactional or @Around doesn't run. With exposeProxy=true, Spring binds the proxy to a ThreadLocal, and you retrieve it via ((MyType) AopContext.currentProxy()).someAdvisedMethod() so the interceptor chain executes. Both are last resorts; refactoring to separate beans is usually cleaner than exposeProxy.

code

java · 17 lines
java
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)
public class AopConfig {}

@Service
public class OrderService {

    @Transactional
    public void placeOrder(Order o) { /* ... */ }

    public void placeBatch(List<Order> orders) {
        for (Order o : orders) {
            // this.placeOrder(o); // BUG: bypasses proxy, no transaction
            ((OrderService) AopContext.currentProxy()).placeOrder(o); // routes through proxy
        }
    }
}

go deeper

for a junior

Know CGLIB vs JDK exists and self-invocation skips advice.

for a middle

Explain proxyTargetClass values and the self-invocation problem.

for a senior

Detail CGLIB/final limitations and exposeProxy plus cleaner alternatives.

for a principal

Weigh weaving vs proxying, context-wide impact on @Transactional/@Cacheable, and Objenesis/no-arg-ctor nuances.

## proxyTargetClass — which proxy technology Spring can build a proxy two ways: - **JDK dynamic proxy**: `java.lang.reflect.Proxy` implements the target's **interfaces**. The proxy is *not* an instance of the concrete class; only interface methods can be advised. Requires ≥1 interface. - **CGLIB proxy**: a runtime-generated **subclass** of the target class. Works with or without interfaces; can advise public/protected methods but **cannot** proxy `final` classes or `final`/`private` methods (they aren't overridable). `proxyTargetClass` attribute: - `false` (default in plain Spring): use JDK proxy **if** the bean implements an interface, else CGLIB. - `true`: **always** use CGLIB. When to force `true`: - Callers autowire the bean by its **concrete class** or call methods not on any interface — a JDK proxy would fail `ClassCastException`/`NoSuchMethod` because it only exposes interfaces. - You want consistent behavior. Spring Boot sets this to `true` by default for exactly this reason. CGLIB caveats: needs a non-final class; a no-arg constructor is called (Spring uses Objenesis to sidestep this in modern versions); `final` methods silently aren't advised. ## exposeProxy — solving self-invocation **Self-invocation problem**: proxies only intercept calls that come *through* the proxy. If code inside a bean calls another of its own methods via `this.foo()`, that call goes directly to the target instance — the proxy is bypassed, so any advice on `foo()` (transactions, caching, custom `@Around`) **does not run**. This is the #1 AOP gotcha. `exposeProxy=true` makes Spring store the current proxy in a `ThreadLocal` for the duration of a proxied call. Inside the bean you can then do: ```java ((MyService) AopContext.currentProxy()).foo(); ``` Now the internal call routes back through the proxy and the advice fires. ### Trade-offs / better options - `AopContext.currentProxy()` couples your business code to Spring AOP internals and throws `IllegalStateException` if `exposeProxy` is off — ugly. - **Preferred fixes**: move `foo()` into a separate bean and inject it; or self-inject the bean (`@Autowired MyService self;` / `ObjectProvider`); or use AspectJ load-time weaving which advises the real object (no proxy, so self-invocation works). Use `exposeProxy` only when refactoring isn't feasible. ## Putting it together ```java @Configuration @EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true) public class AopConfig {} ``` This forces CGLIB proxies and exposes the proxy on the thread. Note both settings affect **all** auto-proxied beans in the context, not just aspects — they also govern `@Transactional`/`@Cacheable` proxies since those share the infrastructure.

  • Why can't a JDK dynamic proxy advise a method that isn't declared on an interface?
    A JDK proxy is generated purely from the interfaces; the proxy instance doesn't extend the concrete class, so only interface-declared methods exist on it. Non-interface methods aren't visible to intercept — you need CGLIB (proxyTargetClass=true).
  • Besides exposeProxy, name a cleaner way to fix self-invocation.
    Extract the advised method into a separate bean and inject it, self-inject the bean via ObjectProvider/@Autowired, or switch to AspectJ load-time/compile-time weaving which advises the actual object rather than a proxy.

saying these in an interview costs you the question

  • Claiming self-invocation still triggers advice by default
  • Saying CGLIB can proxy final classes/methods
  • Thinking exposeProxy changes the proxy type
  • Believing proxyTargetClass only affects @Aspect beans, not @Transactional

context