How do mocking frameworks and AOP frameworks use dynamic proxies under the hood, and what are the trade-offs versus MethodHandles or compile-time weaving?
answer
- Mockito/Hibernate → ByteBuddy subclass; Spring → JDK or CGLIB
- Interceptor chain + proceed() = around advice
- Proxies miss: final, self-invocation, constructors
- MethodHandles > reflective Method.invoke for speed
- AspectJ weaving = no proxy, advises self-calls & final
basics
~20 sFrameworks generate a proxy (interface-based JDK proxy or a subclass via ByteBuddy/CGLIB) and route every call through an interceptor. Mocks record/replay calls there; AOP runs advice there. Alternatives are MethodHandles for faster dispatch and compile-time weaving for zero runtime proxies.
solid answer
~50 sBoth mocking and AOP frameworks intercept method calls. JDK dynamic proxies cover interface targets; for concrete classes frameworks generate a subclass with ByteBuddy (modern; Mockito, Hibernate) or CGLIB. The interceptor sees each call, and a mock records expectations and returns stubbed values, while AOP runs before/after/around advice and then proceeds to the real method. The trade-offs: runtime proxies are flexible and need no build step but add per-call indirection, can't touch final members, suffer self-invocation blind spots, and can clash with the module system. java.lang.invoke MethodHandles/LambdaMetafactory give faster, more direct invocation than reflective Method.invoke and underpin modern interceptor dispatch. Compile-time or load-time weaving (AspectJ) rewrites bytecode directly, so there's no proxy object at all — it can advise final methods, constructors, and field access, and self-calls are intercepted, at the cost of a build/agent step and more complexity. Choose proxies for simplicity, weaving for completeness and performance.
go deeper
Aware that mocking and AOP libraries rely on generated proxies behind the scenes.
Can say Mockito/Spring generate proxies and that interception routes calls to a handler/interceptor.
Explains JDK-vs-subclass selection, the final/self-invocation limits, and that weaving is an alternative; knows ByteBuddy is the modern generator.
Compares proxy interception, MethodHandle dispatch, and compile/load-time weaving on capability, performance, module-system, and maintainability axes, and chooses per context.
## The shared idea: interception Mocking and Aspect-Oriented Programming (AOP) both need to **intercept a method call** and run custom logic instead of, or around, the real body. Dynamic proxies are the most common runtime vehicle. ### Terms - **Mock**: a fake object used in tests that records which methods were called and returns programmed values. - **AOP / advice**: cross-cutting logic (logging, transactions, security) injected around a 'join point' (a method call). 'Advice' = the injected code; 'around advice' wraps the call and decides whether/when to `proceed()`. - **Weaving**: the act of combining advice with target code — at **compile time**, **load time** (via a Java agent that transforms bytecode as classes load), or **runtime** (via proxies). - **MethodHandle** (`java.lang.invoke`): a typed, directly-invokable reference to a method, faster than reflective `Method.invoke`. ## How mocking frameworks use proxies Mockito creates a mock by generating a subclass/implementation (via **ByteBuddy**) whose every method is intercepted. On a call it consults stubbing rules (`when(...).thenReturn(...)`), records the invocation for later `verify(...)`, and returns a default or stubbed value. For interface mocks a JDK proxy would suffice, but to mock concrete classes it must subclass — hence ByteBuddy. This is why Mockito historically **cannot mock final classes/methods** without the inline mock maker (which uses an agent to redefine bytecode). ## How AOP frameworks use proxies Spring AOP wraps a bean in a proxy: a **JDK dynamic proxy** if accessed via an interface, else a **CGLIB** subclass. The proxy's handler/interceptor builds a chain of advice; around-advice calls `proceed()` to continue to the next advice or finally the real method. Limitations follow directly from the proxy model: **self-invocation** (a bean calling its own method via `this`) bypasses the proxy and skips advice; **final** classes/methods can't be subclass-proxied; constructors/field access aren't join points. ## Why MethodHandles matter Reflective `Method.invoke` is flexible but relatively slow and does access checks each call. `java.lang.invoke` **MethodHandles** and `LambdaMetafactory` produce direct, JIT-friendly call sites; many interception/serialization frameworks now dispatch through MethodHandles for performance. A handler can still receive the `Method` for identity but invoke through a cached `MethodHandle`. ## Compile-time / load-time weaving (AspectJ) AspectJ rewrites the **bytecode of the target classes themselves** rather than wrapping them in a separate proxy object. Consequences: - **No proxy object** — the advice lives in the real class, so **self-invocation is advised**, and **final methods, constructors, and even field access** can be join points. - **Costs**: a special compiler or a load-time-weaving Java agent, longer builds, debugging into woven code, and tighter coupling to the weaving toolchain. ## Decision framework (principal-level) | Concern | Runtime proxy (JDK/ByteBuddy) | Weaving (AspectJ) | |---|---|---| | Setup | None / library only | Compiler or agent | | Self-invocation advised | No | Yes | | Final/constructors/fields | No | Yes | | Per-call overhead | Indirection (mitigated by MethodHandles) | Near-native | | Complexity / debuggability | Lower | Higher | | Module-system friction | Possible | Possible (agent) | Use runtime proxies for the common 80% (interface-based services, simple mocks) — they're simple and dependency-light. Reach for weaving when you must advise things proxies structurally can't (final, self-calls, constructors) or when proxy overhead is unacceptable. Use MethodHandles to make whichever interception layer you build fast.
- Why can AspectJ advise final methods and self-invocations when proxies cannot?AspectJ weaves advice into the target class's own bytecode rather than wrapping it in a separate object. There is no proxy to bypass, so internal this-calls are advised, and since it edits the class directly it isn't blocked by final or by needing to subclass.
- What advantage do MethodHandles offer over reflective Method.invoke inside an interceptor?MethodHandles from java.lang.invoke provide direct, JIT-optimizable invocation with access checks done once at lookup, so per-call dispatch is significantly faster than reflective Method.invoke, which is heavier and re-checks access.
saying these in an interview costs you the question
- Saying Spring AOP intercepts self-invocation by default
- Claiming Mockito can mock final classes with the default mock maker
- Treating reflective Method.invoke as equally fast as MethodHandles
- Believing AspectJ uses proxies (it weaves bytecode instead)
- Ignoring the build/agent cost of weaving