Did you ever wonder how mocking frameworks “create” instances of you interfaces? Or how OR-mappers like Hibernate give you entities with lazily loaded references to another of your entities that throw the well known LazyInitializationException when the session is already closed?
The answer is: Dynamic proxies.
As part of the reflection package in the JDK, the classes around java.lang.reflect.Proxy allow you to create instances of interfaces – even if there is no implementation of that interface present in the classpath. These instances are called “proxy-instances”.
Each proxy-instance has assigned an implementation of the interface java.lang.reflect.InvocationHandler. The handler gets informed about every method that is called on the proxy-instance (including arguments, return types etc.). This allows you to dynamically create “runtime implementations” of interfaces, giving them a custom behavior (testing frameworks like JUnit – record, replay, etc.) or just to intercept/modify calls to a “real” instance (AOP-like behavior).
Here is an example to get an idea of how (easy) it works:
Main class
package de.perdoctus.examples; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; /** * This class contains a main method that creates a dynamic proxy. * * @author Christoph Giesche */ public class ProxySample { public static void main(String[] args) { //Create an instance of our InvocationHandler InvocationHandler handler = new UselessHandler(); //Create a "dynamic proxy" of our useless interface Object proxy = Proxy.newProxyInstance( Useless.class.getClassLoader(), new Class[] {Useless.class}, handler); //Cast the proxy instance to our Useless interface Useless uselessProxy = (Useless) proxy; //Call some methods on our instance uselessProxy.getStatus(); uselessProxy.setSomeValue("foobar"); uselessProxy.toString(); } } |
Interface that is used to create proxy-instance
package de.perdoctus.examples; /** * Just a useless interface to demonstrate dynamic proxies. * * @author Christoph Giesche */ public interface Useless { void getStatus(); void setSomeValue(String value); } |
An invocation handler telling you what is going on
package de.perdoctus.examples; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; /** * UselessHandler will handle all method calls to our dynamic proxy. This * handler can be assigned to different proxy instances. * * @author Christoph Giesche */ public class UselessHandler implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { StringBuilder sb = new StringBuilder(); sb.append("Method '") .append(method.getName()) .append("' was called "); if (args != null) { sb.append("with arguments:"); for (Object arg : args) { sb.append('\n'); sb.append(arg.getClass().getSimpleName()) .append(" = ") .append(arg.toString()); } } else { sb.append("without arguments."); } sb.append("\nThe method has the return type '") .append(method.getReturnType()) .append("'\n"); System.out.println(sb.toString()); return null; } } |
Result when running main class
Method 'getStatus' was called without arguments. The method has the return type 'void' Method 'setSomeValue' was called with arguments: String = foobar The method has the return type 'void' Method 'toString' was called without arguments. The method has the return type 'class java.lang.String'