Since version 1.3, JDK has API to implement dynamic proxies based on interfaces.
Dynamic Proxies created by JDK is based on Proxy design pattern
We have to use two important classes from java.lang.reflect package to get started:
This static method of Proxy class is the point where a provided interface is dynamically implemented by Java during runtime:
Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
Where the middle parameter 'interfaces' are to be implemented.
The interface java.lang.reflect.InvocationHandler :
package java.lang.reflect;
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
In this example, we are going to demonstrate how to implement an interface during runtime and what we can achieve by doing that.
Creating an Interface
private static interface MyInterface {
void doSomething ();
}
Implementing InvocationHandler
In this very simple example, we are not going to do much in InvocationHandler#invoke . We just want to see how things work:
public class MyInvocationHandler implements InvocationHandler {
@Override
public Object invoke (Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method);
return null;
}
}
Creating the Dynamic Proxy
public static void main (String[] args) {
MyInvocationHandler handler = new MyInvocationHandler();
MyInterface o = (MyInterface) Proxy.newProxyInstance(
MyInvocationHandler.class.getClassLoader(),
new Class[]{MyInterface.class}, handler);
o.doSomething();
}
Output:
public abstract void com.logicbig.example.MyInterface.doSomething()
What's happening here?
The method call Proxy.newProxyInstance returns an Object created dynamically by Java during runtime time. There's no compile time processing here. The returned Object implements all interfaces provided as second argument. Java uses Reflection to achieve that.
Out of curiosity, let's print the stacktrace in MyInvocationHandler#invoke method:
public class MyInvocationHandler implements InvocationHandler {
@Override
public Object invoke (Object proxy, Method method, Object[] args) throws Throwable {
Arrays.stream(Thread.currentThread()
.getStackTrace())
.forEach(System.out::println);
return null;
}
}
Output:
java.lang.Thread.getStackTrace(Thread.java:1552)
com.logicbig.example.MyInvocationHandler.invoke(MyInvocationHandler.java:13)
com.logicbig.example.$Proxy0.doSomething(Unknown Source)
com.logicbig.example.MyInvocationHandler.main(MyInvocationHandler.java:26)
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
java.lang.reflect.Method.invoke(Method.java:497)
com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
In the 3rd line from the top, the $Proxy0 is the runtime implementation of our interface:
com.logicbig.example.$Proxy0.doSomething(Unknown Source)
The first parameter of the 'invoke' method is the same $Proxy0 object. We didn't print that out because that will cause recursive calls to the invoke method on toString() method. (System.out.println uses toString to print objects). Why recursive? because each call on $Proxy0 will route to our 'invoke' method, even $Proxy0.toString() call.
In the above example, we are just printing a simple message, but in real scenario we will apply some application specific logic based on the Method argument.
What we can achieve with this feature?
- Method interceptors.
- Dynamic Proxy.
- Remote Proxy.
- Decorator design pattern.
- Facade design pattern.
- Bridge design pattern.
- Or whatever you can come up with!
In this series of tutorials we will be exploring some of them.
Example ProjectDependencies and Technologies Used:
|