Friday, October 15, 2010

Java Dynamic Proxies for Testing

I've recently been exposed to the use of Proxies in Java. The Reflection library is actually quite powerful and knowing it will open solutions to things not normally possible.

the java.lang.reflect.Proxy can be  used to interpose between caller and the object being called. The Proxied object has an associated handler and every call to any method goes through the handler. The handler can then do ... anything.





It could:

  • Simply Log information about parameters been passed in and which methods are been called
  • Do Complex argument analysis i.e. Test invariants not in code such as are the arguments actually suitable for the method
  • Modify the arguments or even replace them before passing them onto the real method


All this without modifying the any of the classes you are examining!.


With an Invocation handler all method calls are routed through the handler









Here is an example of how it can be used.

/**
*Processable.class
*/
public interface Processable {

 public boolean process(Object obj);
}
/**
*Processor.class
*/
public class Processor implements Processable {

 @Override
 public boolean process(Object obj) {
  boolean ret = false;
  /**
   * Does Processing Here
   */
  return ret;
 }
}
/**
*Engine.class
*/
public class Engine {

 public static void doStuff(Processable p) {

  p.process("Heres a String");
  // do a lot of stuff with Processor Object maybe even pass it through to
  // other classes
 }
}




I have Processor object which implements Processable and this Processor object is passed to an Engine which does many things with the object.

To Create a Proxy Object I need to make a InvocationHandler which will handle all calls to the proxied object this is done by implementing the InvocationHandler Interface.
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class ProcessorInvocationHandler implements InvocationHandler {

 private Processor m_processor;
 private int count_calls = 0;

 public ProcessorInvocationHandler(Processor p_processor) {
  m_processor = p_processor;
 }

 @Override
 public Object invoke(Object proxy_object, Method method, Object[] params)
   throws Throwable {

  System.out.println(params);
  if (method.getName().equals("process"))
   count_calls++;
  return method.invoke(m_processor, params);
 }

 public int getCount() {
  return count_calls;
 }

}
To Actually Use it you can make a call to Proxy.newInstance , all you need is an interface which represents your class. This is because the object returned will be of type Processable , it will NOT be of type Processor. This important to remember as if the object has no interfaces this won't work.



Processable p = new Processor();
Engine.doStuff(p);

// With Proxy
ProcessorInvocationHandler handler = new ProcessorInvocationHandler(
  new Processor());
Processable p2 = (Processable) Proxy.newProxyInstance(Processor.class
  .getClassLoader(), Processor.class.getInterfaces(), handler);
Engine.doStuff(p2);
System.out.println(handler.getCount());

This can be useful in certain situations. Notice that to add the proxy and reveal information I did not have to change the actual objects both Processor and Engine were unchanged. This is very useful where changing code is difficult on not possible.

Since the InvocationHandler can hold a reference to the real object its possible to call the real method and the Proxy can be used to just trace the code.

With Simple programs this may seem unnecessary but with large code bases  managed by many people its often hard to see bigger pictures this is a very useful tool to see what your object life cycle is in your system.

Limitations
There are however some limitations:

  • To Proxy an interface to the object is needed
  • Proxy is not an instanceOf the object it is proxying. For example in the given scenario Proxy is not instanceof Processor. However Proxy instanceOf Processable will be true.
  • Without interfaces no static methods as well as the Proxied object will only have the interface methods.
  • Proxied Object is for a specific instance of the class you are proxying this means you need to create a new proxy instance for every instance of the object. 


Also here is a link to a java tech article also explaining this Java Developer Article Interposing Java

No comments:

Post a Comment