Java Dynamic Proxy: What is a Proxy and How can We Use It
Learn all about Java dynamic proxies: what they are, when to use, how and when to use in code.
Join the DZone community and get the full member experience.
Join For FreeWhat is a Proxy?
Proxy is a design pattern. We create and use proxy objects when we want to add or modify some functionality of an already existing class. The proxy object is used instead of the original one. Usually, the proxy objects have the same methods as the original one and in Java proxy classes usually extend the original class. The proxy has a handle to the original object and can call the method on that.
This way proxy classes can implement many things in a convenient way:
- logging when a method starts and stops
- perform extra checks on arguments
- mocking the behavior of the original class
- implement lazy access to costly resources
Without modifying the original code of the class. (The above list is not extensive, it only list some examples).
In practical applications, the proxy class does not directly implement the functionality. Following the single responsibility principle, the proxy class does only proxying and the actual behavior modification is implemented in handlers. When the proxy object is invoked instead of the original object, the proxy decides if it has to invoke the original method or some handler. The handler may do its task and may also call the original method.
Even though the proxy pattern does not only apply to situations when the proxy object and proxy class is created during run-time, this is an especially interesting topic in Java. In this article, I will focus on these proxies.
This is an advanced topic because it requires the use of the reflection class, or bytecode manipulation or compiling Java code generated dynamically. Or all of these. To have a new class not available as a bytecode yet during run-time will need the generation of the bytecode, and a class loader that loads the bytecode. To create the bytecode, you can use cglib or bytebuddy or the built-in Java compiler.
When we think about the proxy classes and the handlers they invoke, we can understand why the separation of responsibilities, in this case, is important. The proxy class is generated during run-time, but the handler invoked by the proxy class can be coded in the normal source code and compiled along the code of the whole program (compile time).
How Can We Use This In Our Code?
The easiest way to do this is to use the java.lang.reflect.Proxy
class, which is part of the JDK. That class can create a proxy class or directly an instance of it. The use of the Java built-in proxy is easy. All you need to do is implement a java.lang.InvocationHandler
, so that the proxy object can invoke it. The InvocationHandler
interface is extremely simple. It contains only one method: invoke()
. When invoke()
is invoked, the arguments contain the original object, which is proxied, the method that was invoked (as a reflection Method
object) and the object array of the original arguments. A sample code demonstrates the use:
package proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class JdkProxyDemo {
interface If {
void originalMethod(String s);
}
static class Original implements If {
public void originalMethod(String s) {
System.out.println(s);
}
}
static class Handler implements InvocationHandler {
private final If original;
public Handler(If original) {
this.original = original;
}
public Object invoke(Object proxy, Method method, Object[] args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException {
System.out.println("BEFORE");
method.invoke(original, args);
System.out.println("AFTER");
return null;
}
}
public static void main(String[] args){
Original original = new Original();
Handler handler = new Handler(original);
If f = (If) Proxy.newProxyInstance(If.class.getClassLoader(),
new Class[] { If.class },
handler);
f.originalMethod("Hallo");
}
}
If the handler wants to invoke the original method on the original object, it has to have access to it. This is not provided by the Java proxy implementation. You have to pass this argument to the handler instance yourself in your code. (Note that there is an object usually named proxy passed as an argument to the invocation handler. This is the proxy object that the Java reflection dynamically generate and not the object we want to proxy.) This way you are absolutely free to use a separate handler object for each original class or use some shared object that happens to know some way which original object to invoke if there is any method to invoke at all.
As a special case, you can create an invocation handler and a proxy of an interface that does not have any original object. Even more, it is not needed to have any class to implement the interface in the source code. The dynamically created proxy class will implement the interface.
What should you do if the class you want to proxy does not implement an interface? In that case, you have to use some other proxy implementation. We will look at that in a coming post.
Published at DZone with permission of Peter Verhas, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments