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'