skip to content

Explain why this(ConcreteClass) can silently fail to match while target(ConcreteClass) works, and how proxy type selection (JDK vs CGLIB) drives it.

level: seniorimportance: must knowfreq 40%

answer

  1. JDK proxy = interfaces only, not concrete class
  2. CGLIB = subclass of concrete class
  3. this(Impl) dark under JDK proxy
  4. target() proxy-agnostic for concrete
  5. Boot defaults proxyTargetClass=true

basics

~20 s

With a JDK dynamic proxy the proxy implements only the target's interfaces, so it is not an instance of the concrete class — this(ConcreteClass) fails. target(ConcreteClass) checks the real object, which is the concrete class, so it matches. CGLIB proxies subclass the concrete class, so both match.

solid answer

~40 s

this() tests the proxy's type; target() tests the real target's type. Spring picks the proxy kind per bean: a **JDK dynamic proxy** when the bean has interfaces (default), which implements those interfaces but does not extend the concrete class — so the proxy is not an instanceof the impl class, and this(Impl) never matches. target(Impl) still matches because the wrapped object genuinely is the impl. When there is no interface, or proxyTargetClass=true, or @EnableAspectJAutoProxy(proxyTargetClass = true), Spring uses a **CGLIB** proxy that is a runtime subclass of the concrete class, so both this(Impl) and target(Impl) match. Practical guidance: match on interfaces or use target() for concrete types; only reach for this() when you truly need the proxy reference; and know Spring Boot defaults proxyTargetClass=true, which changes this behaviour.

code

java · 16 lines
java
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true) // force CGLIB → this(Impl) now matches
class AopConfig {}

// With the default (JDK proxy) and OrderServiceImpl implements OrderService:
@Aspect @Component
class ProxyKindDemo {
    @Before("this(com.example.OrderService)")      // OK under JDK + CGLIB
    void onInterfaceProxy() {}

    @Before("this(com.example.OrderServiceImpl)")  // SILENT no-op under JDK proxy!
    void onConcreteProxy() {}

    @Before("target(com.example.OrderServiceImpl)")// OK under JDK + CGLIB (safe)
    void onConcreteTarget() {}
}

go deeper

for a junior

Too advanced to lead with; just recognise JDK vs CGLIB exist.

for a middle

State the rule: this(Impl) can silently miss under JDK proxying; use target() or the interface.

for a senior

Explain the mechanism (Proxy subclass vs CGLIB subclass), how Spring/Boot chooses, and give the fix matrix.

for a principal

Set a codebase policy on proxy strategy and marker-interface pointcuts, and anticipate refactors that flip proxy kind.

## Setup: the two proxy mechanisms Spring AOP builds a proxy around each advised bean using one of two technologies: ### 1. JDK dynamic proxy Uses `java.lang.reflect.Proxy`. The generated proxy class: - **implements the target bean's interfaces**, and - extends `java.lang.reflect.Proxy` (NOT the concrete bean class). So for `class OrderServiceImpl implements OrderService`, the proxy `instanceof OrderService` is `true`, but `instanceof OrderServiceImpl` is `false`. ### 2. CGLIB proxy Generates a **runtime subclass of the concrete bean class** and overrides its methods. So the proxy `instanceof OrderServiceImpl` is `true` (and it also passes for the interfaces). ## How Spring chooses - If the bean **implements at least one interface** and `proxyTargetClass` is false → **JDK dynamic proxy**. - If the bean has **no interface**, or `proxyTargetClass=true`, or a technical reason forces it → **CGLIB**. - `@EnableAspectJAutoProxy(proxyTargetClass = true)` or the XML `<aop:config proxy-target-class="true">` forces CGLIB globally. - **Spring Boot** sets `spring.aop.proxy-target-class=true` by default, so Boot apps CGLIB-proxy by default — which is exactly why the `this(Impl)` gotcha often does NOT bite in Boot, but does bite in a plain Spring app or when interfaces + JDK proxies are in play. ## The match/no-match table Given `OrderServiceImpl implements OrderService`: | Pointcut | JDK proxy | CGLIB proxy | |----------|-----------|-------------| | `this(OrderService)` | match | match | | `this(OrderServiceImpl)` | **no match** | match | | `target(OrderService)` | match | match | | `target(OrderServiceImpl)` | match | match | The single 'no match' cell is the entire gotcha: **`this()` against a concrete class under a JDK proxy**. ## Why 'silently' There is no error — the pointcut simply selects no join points, so the advice never runs. Debugging looks like 'my aspect is ignored'. Common triggers: someone types the impl class into `this()`, or refactors a bean to add an interface (flipping it from CGLIB to JDK proxying) and existing `this(Impl)` pointcuts go dark. ## Fixes / choices 1. **Match the interface**: `this(OrderService)` — works under both proxy kinds. Best when your abstraction is the interface. 2. **Use `target()` for concrete types**: `target(OrderServiceImpl)` — works under both. 3. **Force CGLIB**: `@EnableAspectJAutoProxy(proxyTargetClass = true)` (or Boot's default) — then even `this(OrderServiceImpl)` matches. 4. Prefer marker interfaces (`target(com.example.Auditable)`) so matching is intent-revealing and proxy-agnostic. ## Related self-invocation note Both proxies share the classic limitation: an internal `this.otherMethod()` call from inside the target bypasses the proxy entirely, so no `this()`/`target()`/any advice fires on it — because the call goes to the raw target, not through the proxy. That is orthogonal to this gotcha but frequently confused with it. ## When to use which - Public API advised by capability → interface-based `this()`/`target()` on the interface. - Concrete-class-specific advice → `target(Concrete)` (proxy-agnostic) rather than `this(Concrete)`. - Need the proxy object itself (e.g. to re-enter through advice, or read AOP metadata) → `this()`, and ensure the type you name is one the proxy actually exposes.

  • A team adds an interface to a previously interface-less bean and an existing this(Impl) aspect stops firing. Why?
    Adding an interface flips Spring from CGLIB (proxy is a subclass of Impl) to a JDK dynamic proxy (proxy implements only the interface). The proxy is no longer an instanceof Impl, so this(Impl) no longer matches. Fix by matching the interface, using target(Impl), or forcing proxyTargetClass=true.
  • Does target() ever fail where this() succeeds?
    target() reflects the real object's type, which is stable regardless of proxy kind, so for concrete/interface types the target implements it always matches. this() can succeed on a proxy-only type (e.g. SpringProxy/Advised marker interfaces implemented by the proxy but not the target) where target() would fail — those interfaces exist only on the proxy.

context