Implementing a dynamic proxy

For example, let's write a dynamic proxy that counts the number of invocations of the methods of List.

A dynamic proxy is created via the Proxy.newProxyInstance() method. The newProxyInstance() methods takes three parameters:

  • ClassLoader: This is used to load the dynamic proxy class
  • Class<?>[]: This is the array of interfaces to implement
  • InvocationHandler: This is the invocation handler to dispatch method invocations to

Check out this example:

List<String> listProxy = (List<String>) Proxy.newProxyInstance(
List.class.getClassLoader(), new Class[] {
List.class}, invocationHandler);

This snippet of code returns a dynamic implementation of the List interface. Further, all invocations via this proxy will be dispatched to the invocationHandler instance.

Mainly, a skeleton of an InvocationHandler implementation looks as follows:

public class DummyInvocationHandler implements InvocationHandler {

@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
...
}
}

Since we want to count the number of invocations of the methods of List, we should store all methods signatures and the number of invocations for each of them. This can be accomplished via Map initialized in the constructor of CountingInvocationHandler as follows (this is our InvocationHandler implementation, and invocationHandler is an instance of it):

public class CountingInvocationHandler implements InvocationHandler {

private final Map<String, Integer> counter = new HashMap<>();
private final Object targetObject;

public CountingInvocationHandler(Object targetObject) {
this.targetObject = targetObject;

for (Method method:targetObject.getClass().getDeclaredMethods()) {
this.counter.put(method.getName()
+ Arrays.toString(method.getParameterTypes()), 0);
}
}
...
}

The targetObject field holds the implementation of the List interface (in this case, ArrayList).

And we create a CountingInvocationHandler instance as follows:

CountingInvocationHandler invocationHandler 
= new CountingInvocationHandler(new ArrayList<>());

The invoke() method simply counts the invocations and invokes Method with the specified arguments:

@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {

Object resultOfInvocation = method.invoke(targetObject, args);
counter.computeIfPresent(method.getName()
+ Arrays.toString(method.getParameterTypes()), (k, v) -> ++v);

return resultOfInvocation;
}

Finally, we expose a method that returns the number of invocations for the given method:

public Map<String, Integer> countOf(String methodName) {

Map<String, Integer> result = counter.entrySet().stream()
.filter(e -> e.getKey().startsWith(methodName + "["))
.filter(e -> e.getValue() != 0)
.collect(Collectors.toMap(Entry::getKey, Entry::getValue));

return result;
}

The code bundled to this book glues these snippets of code in a class named CountingInvocationHandler.

At this moment, we can use listProxy to call several methods, as follows:

listProxy.add("Adda");
listProxy.add("Mark");
listProxy.add("John");
listProxy.remove("Adda");
listProxy.add("Marcel");
listProxy.remove("Mark");
listProxy.add(0, "Akiuy");

And let's see how many times we invoked the add() and remove() methods:

// {add[class java.lang.Object]=4, add[int, class java.lang.Object]=1}
invocationHandler.countOf("add");

// {remove[class java.lang.Object]=2}
invocationHandler.countOf("remove");
Since the add() method has been invoked via two of its signatures, the resulted Map contains two entries.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset