Generically chain dynamic proxies

Add AOP concepts to your application

1 2 3 4 Page 3
Page 3 of 4
 String[] interceptorClasses = {"MyDebugInterceptor", "MyAnotherInterceptor"};
IMyBusinessObject aIMyBusinessObject =
   (IMyBusinessObject)MyProxyFactory.getProxyObject
      ("MyBusinessObject", interceptorClasses);
String ret = aIMyBusinessObject.doExecute("Hello World");

Our intention in this approach is to use only one instance of GenericInvocationHandler and proxy. Let's see our new GenericInvocationHandler code:

 

public class GenericInvocationHandler implements java.lang.reflect.InvocationHandler {

private Object realtarget = null; public void setRealTarget(Object realtarget_) { this.realtarget = realtarget_; } Object[] methodInterceptors = null; public void setMethodInterceptors (Object[] methodInterceptors_) { this.methodInterceptors = methodInterceptors_; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { Object[] retInterceptBefore = null; if ( methodInterceptors != null && methodInterceptors.length > 0 ) {

retInterceptBefore = new Object[methodInterceptors.length]; for ( int i= methodInterceptors.length - 1; i >= 0; i-- ) { if ( methodInterceptors[i] != null ) { retInterceptBefore[i] = ((IMethodInterceptor)methodInterceptors[i]). interceptBefore(proxy, method,args, realtarget ); } } } Object retObject = method.invoke(realtarget, args); if ( methodInterceptors != null ) { for ( int i= 0; i < methodInterceptors.length; i++ ) { if ( methodInterceptors[i] != null ) { ((IMethodInterceptor)methodInterceptors[i]). interceptAfter(proxy, method, args, realtarget, retObject, retInterceptBefore[i] ); }

} } return retObject; } catch(InvocationTargetException e) { throw e.getTargetException(); } catch(Exception e) { throw e; } } }

Our GenericInvocationHandler takes the array of method interceptors (instead of a single interceptor like Approach 1) and before calling the method on the business interface, it loops through the interceptor array and calls each interceptor's interceptBefore() method. Similarly, after the method call, GenericInvocationHandler calls the interceptAfter() method in a loop. Please note that the loop goes in a reverse direction for interceptBefore() and forward for interceptAfter().

The code for MyProxyFactory also changes, but the getInterceptor() and getTargetObject() methods remain the same. The following code snippet comes from our new MyProxyFactory:

 

public static Object getProxyObject( String className, String[] interceptors ) throws Throwable {

Object inputObject = getTargetObject(className); if ( interceptors != null && interceptors.length > 0 ) { return getProxyObject(inputObject, interceptors); } else { return inputObject; } } private static Object getProxyObject(Object inObject, String[] interceptors) throws Throwable {

GenericInvocationHandler invocationHandler = new GenericInvocationHandler(); Object[] interceptorObjects = getInterceptors(interceptors); invocationHandler.setRealTarget(inObject); invocationHandler.setMethodInterceptors(interceptorObjects);

return Proxy.newProxyInstance (inObject.getClass().getClassLoader(), inObject.getClass().getInterfaces(), invocationHandler) ; } private static Object[] getInterceptors(String[] interceptors) throws Exception {

Object[] objInterceptors = new Object[interceptors.length]; for ( int i=0; i < interceptors.length; i++ ) { objInterceptors[i] = getInterceptor(interceptors[i]); } return objInterceptors; }

From the above code snippet, we can see that there is only one instance of GenericInvocationHandler. In addition, only one instance of the proxy object is created and does not vary with the number of interceptors. Thus, the memory used is less; plus, Approach 2 is slightly faster than Approach 1. Later, I will compare results of all the approaches described with a simple test.

Some might point out that in Approach 2 we just loop through all the interceptors in GenericInvocationHandler instead of MyProxyFactory. The important point to note: In Approaches 1 and 2, the number of created interceptor instances is the same. In Approach 1, additional GenericInvocationHandlers and proxy objects are also created inside the loop. But in Approach 2, only one instance of GenericInvocationHandler and the proxy object is created.

The class diagram of Approach 2 is shown in Figure 3.

Figure 3. Generically chain dynamic proxies: Approach 2 class diagram

Approach 1 and Approach 2 can be made more useful by adding in the IMethodInterceptor interface a method (e.g., interceptException()) that can intercept any exception raised by the target (GenericInvocationHandler would also require modification). In addition, we can make different interfaces for all the three methods (interceptBefore(), interceptAfter(), and interceptException()). I advise you to break the interface into different smaller interfaces. For example, if we want to intercept only before executing the method, then only the interface containing the method interceptBefore() would be used and we need not worry about the implementation of the other methods. In aspect-oriented programming (AOP), intercepting in different parts of a program unit represents advice. So a separate interface can be created for before advice (compare to the interceptBefore() method), after return advice (compare to interceptAfter()), or throws advice (compare to interceptException()). Another type of advice, which is more general and flexible, can do all types of method interceptions together. But since it is a general approach, the developer's responsibility also increases (developer needs to forward the method call to next target). This advice type is called around advice. Approach 3 (discussed later) can be compared with AOP's around advice.

Approaches 1 and 2 both have limitations and cannot leverage all the advantages of the dynamic proxy without major modification. For example, since the interceptBefore() and interceptAfter() methods are called discretely, spanning some control between interceptBefore() and interceptAfter() would prove difficult. Let's say we want to write an interceptor that will make the target business object synchronized before invoking it. We want to achieve the following:

 

synchronized(realtarget) {

retObject = method.invoke(target, args); }

In Approach 3, we will learn how.

Generically chain a dynamic proxy: Approach 3

In Approach 3, the client code resembles that of Approaches 1 and 2:

 String[] invocationHandlers = {"MyDebugInvocationHandler", 
   "MyAnotherInvocationHandler"};
IMyBusinessObject aIMyBusinessObject =
   (IMyBusinessObject)MyProxyFactory.getProxyObject
      ("MyBusinessObject", invocationHandlers);
String ret = aIMyBusinessObject.doExecute("Hello World");

In this approach, we define the IMyInvocationHandler interface as follows:

 public interface IMyInvocationHandler {
   void setTarget(Object target_);
   void setRealTarget(Object realtarget_);
}

We also define an abstract class that implements the IMyInvocationHandler and java.lang.reflect.InvocationHandler interfaces:

 

public abstract class AMyInvocationHandler implements IMyInvocationHandler, java.lang.reflect.InvocationHandler {

protected Object target = null; protected Object realtarget = null;

public void setTarget(Object target_) { this.target = target_; } public void setRealTarget(Object realtarget_) { this.realtarget = realtarget_; } }

Approach 3 includes no GenericInvocationHandler class or IMethodInterceptor interface. Instead, our interceptors extend the abstract AMyInvocationHandler class. Let's look into our two interceptors. They now provide an implementation of the invoke() method defined by the java.lang.reflect.InvocationHandler interface:

  1.  

    public class MyDebugInvocationHandler extends AMyInvocationHandler {

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

    try { System.out.println("MyDebugInterceptor: Before execute method : " + method.getName()); Object retObject = method.invoke(target, args); System.out.println("MyDebugInterceptor: After execute method : " + method.getName()); return retObject; } catch(InvocationTargetException e) { throw e.getTargetException(); } catch(Exception e) { throw e; } } }

  2.  

    public class MyAnotherInvocationHandler extends AMyInvocationHandler {

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

    try { System.out.println("MyAnotherInvocationHandler: Before execute method : " + method.getName()); if ( method.getName().equals("doExecute") && args != null && args.length >= 1 ) { if ( args[0] instanceof String ) { args[0] = args[0] + " Modified by MyAnotherInvocationHandler"; } } Object retObject = method.invoke(target, args); System.out.println("MyAnotherInvocationHandler: After execute method : " + method.getName()); return retObject; } catch(InvocationTargetException e) { throw e.getTargetException(); } catch(Exception e) { throw e; } } }

In the MyDebugInvocationHandler and MyAnotherInvocationHandler classes, it is important to note that no distinct interface or interface methods are available for intercepting before or after doExecute(), or for throwing any exceptions with respect to the method invocation. Consider the Servlet's Filter interface, where we have only a doFilter() method; there are no doBefore() or doAfter() methods in the Filter interface. Approach 3 has similar functionality.

Approach 3 offers the developer more flexibility than the other approaches. For example, using Approach 3, let's see how we can achieve synchronization. We just have to implement a synchronization handler that will extend AMyInvocationHandler and perform the synchronization logic inside the invoke() method. To test the behavior, we need to ensure that multiple threads are accessing the doExecute() method simultaneously on the same business object instance. The sample synchronization handler code is provided below:

 

public class MySynchronizeInvocationHandler extends AMyInvocationHandler {

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object retObject = null; synchronized(realtarget) { retObject = method.invoke(target, args); } return retObject; } }

Now let's look into Approach 3's MyProxyFactory code snippet:

 

public static Object getProxyObject( String className, String[] invocationHandlers ) throws Throwable {

Object inputObject = getTargetObject(className); if ( invocationHandlers != null && invocationHandlers.length > 0 ) {

Object inputProxiedObject = inputObject; for ( int i=0; i < invocationHandlers.length; i++ ) { AMyInvocationHandler myInvocationHandler = (AMyInvocationHandler)getInvocationHandler (invocationHandlers[i]); inputProxiedObject = getProxyObject(inputObject, myInvocationHandler, inputProxiedObject); } return inputProxiedObject; } else { return inputObject;

} } public static Object getProxyObject( Object inputObject, Object[] invocationHandlers ) throws Throwable {

if ( invocationHandlers != null && invocationHandlers.length > 0 ) {

Object inputProxiedObject = inputObject; for ( int i=0; i < invocationHandlers.length; i++ ) { inputProxiedObject = getProxyObject(inputObject, (AMyInvocationHandler)invocationHandlers[i], inputProxiedObject); }

return inputProxiedObject; } else { return inputObject; } } private static Object getProxyObject(Object inObject, AMyInvocationHandler myInvocationHandler, Object inProxiedObject) throws Throwable {

if ( myInvocationHandler == null ) { return inProxiedObject; } myInvocationHandler.setTarget(inProxiedObject); myInvocationHandler.setRealTarget(inObject);

return Proxy.newProxyInstance (inObject.getClass().getClassLoader(), inObject.getClass().getInterfaces(), myInvocationHandler) ; }

The code snippet is nearly the same as Approach 1's MyProxyFactory. This class also loops through the array of invocation handlers and creates the instance of each invocation handler and the proxy object. In Approach 1, the instances of GenericInvocationHandler, the interceptors, and the proxy objects are created inside a loop. In Approach 3, only the invocation handlers and the proxy object are created inside a loop (as there is no separate interceptor).

In the above code snippet, I also introduced another public, static method in the MyProxyFactory class, defined below:

 public static Object getProxyObject
   ( Object inputObject, Object[] invocationHandlers )
1 2 3 4 Page 3
Page 3 of 4