Walk through how Proxy.newProxyInstance and InvocationHandler work together to produce a working proxy at runtime.
answer
- newProxyInstance(loader, interfaces[], handler)
- generated class extends Proxy, implements interfaces
- all calls -> invoke(proxy, method, args)
- method.invoke(target, args) to delegate
- equals/hashCode/toString also routed
basics
~10 sProxy.newProxyInstance(classLoader, interfaces, handler) generates a class implementing those interfaces and returns an instance. Every interface method call is dispatched to handler.invoke(proxy, method, args), where you add behavior and usually call method.invoke(target, args) to delegate.
solid answer
~50 sYou call Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h). The JDK synthesizes (once per interface set / loader, then caches) a class that extends java.lang.reflect.Proxy and implements all the given interfaces. It returns a new instance holding a reference to your InvocationHandler. When any interface method is called on the proxy, the generated method body forwards to h.invoke(Object proxy, Method method, Object[] args): 'method' is the reflected Method, 'args' the arguments (autoboxed), 'proxy' the proxy itself. Inside invoke you run cross-cutting logic and typically delegate with method.invoke(target, args), returning its result (which is what the caller receives). In Spring AOP the handler is JdkDynamicAopProxy: its invoke assembles the matching advice chain (a MethodInvocation), runs the interceptors, then calls the real target. equals, hashCode, and toString are also routed to invoke — you must handle them or delegate carefully.
code
java · 22 linespublic interface Repo { String find(int id); }
class RealRepo implements Repo {
public String find(int id) { return "row-" + id; }
}
Repo target = new RealRepo();
Repo proxy = (Repo) Proxy.newProxyInstance(
Repo.class.getClassLoader(),
new Class<?>[]{ Repo.class },
(p, method, args) -> {
long start = System.nanoTime();
try {
return method.invoke(target, args); // delegate
} catch (InvocationTargetException e) {
throw e.getCause(); // unwrap real exception
} finally {
System.out.println(method.getName() + " took " + (System.nanoTime() - start));
}
});
String r = proxy.find(7); // prints timing, returns "row-7"go deeper
Recognize the three arguments and that invoke handles every call.
Describe delegation via method.invoke and returning its result.
Explain the generated-class shape, InvocationTargetException unwrapping, and Object-method routing.
Map it to JdkDynamicAopProxy's interceptor chain and reason about recursion/self-invocation pitfalls.
## The API ```java Object proxy = Proxy.newProxyInstance( classLoader, // where to define the generated class new Class<?>[]{ MyService.class }, // interfaces to implement invocationHandler); // receives every call ``` - **classLoader** — must be able to 'see' all the interfaces; usually the target's or the interface's loader. - **interfaces** — the proxy will `implement` all of them. Everything is interface-based; there is no way to pass a concrete class here. - **InvocationHandler** — a single functional-style interface with one method. ## What the JDK generates At runtime the JDK builds (and **caches** per class-loader + interface-set) a class roughly like `com.sun.proxy.$Proxy0` that: - `extends java.lang.reflect.Proxy` (fixed superclass — this is exactly why a JDK proxy can't also extend your concrete class), - `implements` each interface you passed, - holds the `InvocationHandler` (stored in the `Proxy.h` field), - for **every** interface method, generates a body that calls `h.invoke(this, <Method>, args)`. Because `java.lang.reflect.Proxy` is a fixed superclass and Java has single inheritance, the proxy **cannot** be a subclass of your implementation class — hence 'not assignable to the concrete class.' ## The dispatch: InvocationHandler.invoke ```java Object invoke(Object proxy, Method method, Object[] args) throws Throwable; ``` - **proxy** — the proxy instance (rarely used; beware calling methods on it -> re-enters invoke -> possible infinite recursion). - **method** — the `java.lang.reflect.Method` being called; use `method.getName()`, annotations, etc. - **args** — argument array; primitives are autoboxed; `null` if the method has no parameters. A typical delegating handler: ```java class LoggingHandler implements InvocationHandler { private final Object target; LoggingHandler(Object target) { this.target = target; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("before " + method.getName()); try { return method.invoke(target, args); // delegate to real object } finally { System.out.println("after " + method.getName()); } } } ``` Whatever `invoke` returns is what the original caller receives. If `method.invoke` throws, the underlying cause is wrapped in `InvocationTargetException` — unwrap with `getTargetException()`/`getCause()` and rethrow to preserve the real exception. ## Object methods `equals`, `hashCode`, and `toString` are **also** dispatched to `invoke`. If you don't handle them, delegating them to the target (or implementing proxy identity semantics) matters — otherwise equality/logging can behave oddly. ## How Spring uses it Spring's `JdkDynamicAopProxy` **is** an `InvocationHandler`. Its `invoke`: 1. Special-cases `equals`/`hashCode` and some AOP infrastructure methods. 2. Builds the **advisor chain** matching this method (`List<MethodInterceptor>`). 3. Wraps it in a `ReflectiveMethodInvocation` and calls `proceed()`, which walks the interceptors and finally invokes the target. ## Gotchas - **Self-invocation**: if the target calls its own method via `this.otherMethod()`, the call never leaves the target, so it bypasses the proxy and its advice. - **Recursion**: calling methods on the `proxy` argument inside `invoke` re-enters `invoke`. - **Only interface methods** are proxied; anything not on a passed interface isn't reachable through the proxy. - Generated proxy classes are cached, so repeated `newProxyInstance` for the same interfaces is cheap.
- Why can a JDK proxy never be assignable to the concrete target class?The generated class already extends java.lang.reflect.Proxy, and Java has single inheritance — so it can only implement interfaces, never also extend your impl class.
- What exception wraps a failure thrown by the target during method.invoke, and how do you preserve the original?InvocationTargetException wraps it; call getTargetException()/getCause() and rethrow that cause so callers see the real exception.
saying these in an interview costs you the question
- Thinking invoke only receives business methods, not equals/hashCode/toString.
- Calling methods on the proxy argument inside invoke (infinite recursion).
- Forgetting to unwrap InvocationTargetException, leaking a reflection wrapper.
- Believing a new proxy class is generated on every call rather than cached.