Generically chain dynamic proxies

Add AOP concepts to your application

1 2 3 4 Page 2
Page 2 of 4

Generically chain a dynamic proxy: Approach 1

Wouldn't it be nice if our client could call the business object using the following piece of code?

 

String[] interceptorClasses = {"MyDebugInterceptor", "MyAnotherInterceptor"}; IMyBusinessObject aIMyBusinessObject = (IMyBusinessObject)MyProxyFactory.getProxyObject

("MyBusinessObject", interceptorClasses); String ret = aIMyBusinessObject.doExecute("Hello World");

The intention in the code above is to provide the class names of the interceptors as a java.lang.String array and to provide the business class name inside a method of the MyProxyFactory class. We expect that MyProxyFactory will create the business object, wrap it using the interceptors passed within the getProxyObject() method as the second parameter, and return us the wrapped proxy object. Now if we invoke the doExecute() business method, all the interceptors should execute in a chain and the actual method should be finally invoked.

It would also be nice if MyDebugInterceptor and MyAnotherInterceptor were generic and didn't vary with our business interface (IMyBusinessObject). To achieve this functionality, we now examine the following steps.

For this approach, we assume method interception will be done independently, before and after business method execution.

Let's define an interface for our interceptors:

 public interface IMethodInterceptor {
   Object interceptBefore(Object proxy, Method method, 
      Object[] args, Object realtarget);
   void interceptAfter(Object proxy, Method method, 
      Object[] args, Object realtarget, Object retObject, 
      Object interceptBeforeReturnObject);
}

Our intention is to call the interceptBefore() method before executing any of our business methods and the interceptAfter() method after successfully executing our business methods (without throwing any exceptions).

Now we write two implementations of IMethodInterceptor:

 

public class MyDebugInterceptor implements IMethodInterceptor {

public Object interceptBefore(Object proxy, Method method, Object[] args, Object realtarget) {

System.out.println("MyDebugInterceptor: Going to execute method : "); return null; } public void interceptAfter(Object proxy, Method method, Object[] args, Object realtarget, Object retObject, Object interceptBefore) {

System.out.println("MyDebugInterceptor: After execute method : " ); } }

In the first implementation above, MyDebugInterceptor's interceptBefore() method intercepts the request before the method dispatches to the target object. interceptBefore() just prints a line to the console.

 

public class MyAnotherInterceptor implements IMethodInterceptor {

public Object interceptBefore(Object proxy, Method method, Object[] args, Object realtarget) {

System.out.println("MyAnotherInterceptor: Going to execute method : "); if ( method.getName().equals("doExecute") && args != null && args.length >= 1 ) {

if ( args[0] instanceof String ) { args[0] = args[0] + " Modified by MyAnotherInterceptor"; } return null; } }

public void interceptAfter(Object proxy, Method method, Object[] args, Object realtarget, Object retObject, Object interceptBefore) {

System.out.println("MyAnotherInterceptor: After execute method : "); } }

In this second implementation, MyAnotherInterceptor's interceptBefore() method also intercepts the request before the method dispatches to the target object. This time, it modifies the request parameter and changes its value (if the method name is doExecute()). It appends the extra string "Modified by MyAnotherInterceptor" in the existing input string parameter.

The next step is to write a generic invocation handler that can deal with the interceptors we have just written:

 

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

private Object target = null; public void setTarget(Object target_) { this.target = target_; } private Object realtarget = null; public void setRealTarget(Object realtarget_) { this.realtarget = realtarget_; } IMethodInterceptor methodInterceptor = null; public void setMethodInterceptor (IMethodInterceptor methodInterceptor_) { this.methodInterceptor = methodInterceptor_; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { Object interceptBeforeReturnObject = null; if ( methodInterceptor != null ) { interceptBeforeReturnObject = methodInterceptor.interceptBefore (proxy, method, args, realtarget ); } Object retObject = method.invoke(target, args); if ( methodInterceptor != null ) { methodInterceptor.interceptAfter (proxy, method, args, realtarget, retObject, interceptBeforeReturnObject ); } return retObject; } catch(InvocationTargetException e) { throw e.getTargetException(); }

catch(Exception e) { throw e; } } }

Look how interceptBefore() and interceptAfter() on the IMethodInterceptor interface have been plugged before and after the method invocation, respectively.

The next step is to write the MyProxyFactory class, which creates the proxies and chains them generically. This particular step should give you an idea of how to design your own framework using dynamic proxies:

 

public class MyProxyFactory {

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

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

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

private static Object getProxyObject(Object inObject, String interceptor,Object inProxiedObject) throws Throwable {

GenericInvocationHandler invocationHandler = new GenericInvocationHandler(); IMethodInterceptor interceptorObject = (IMethodInterceptor)getInterceptor(interceptor); if ( interceptor == null ) { return inProxiedObject; } invocationHandler.setTarget(inProxiedObject); invocationHandler.setRealTarget(inObject); invocationHandler.setMethodInterceptor(interceptorObject);

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

private static Object getInterceptor( String interceptors ) throws Exception { //... //From the class name return the class instance. //You can use Class.forName and newInstance() method on Class //to return the Object instance. } private static Object getTargetObject( String className ) throws Exception { //... //From the class name return the class instance. //You can use Class.forName and newInstance() method on Class //to return the Object instance. } }

In this code's public static Object getProxyObject(String className, String[] interceptors) method, the iteration that goes through all the interceptors calls each interceptor's private static getProxyObject method, which creates an instance of GenericInvocationHandler every time. The method sets the target object and the interceptor on the instance of GenericInvocationHandler and passes the instance in the Proxy class's static newProxyInstance() method.

When the client code invokes the public static getProxyObject method, for the first iteration, it calls the private getProxyObject() with the following three parameters:

  1. The instance of the business object
  2. First interceptor name as a java.lang.String
  3. The same instance of the business object passed in the first parameter

A new proxy object wraps the instance of the business object with the first interceptor in the interceptor chain, and the proxy object is returned.

In the second iteration, the public static getProxyObject method then calls private getProxyObject with the following three parameters:

  1. The business object instance
  2. Second interceptor name as a java.lang.String
  3. The proxy object that wrapped the business object instance with the first interceptor

A new proxy object wraps the first proxy object (returned from the loop's first iteration) with the second interceptor in the interceptor chain, and the newly created proxy object is returned.

You should now get the idea: the chaining occurs generically inside the MyProxyFactory class. It is also important to note that, as the number of interceptors increases, the number of GenericInvocationHandlers and proxy instances also increase (as these have been created within a loop).

In the example, the class name is passed as java.lang.String, and Class.forName is used; in addition, the newInstance() method is used to instantiate the target class and also the interceptors. In a real implementation, using Class.forName and the newInstance() method every time can be problematic. We may want to set some variables in our business class or interceptors before invoking. Or we may want some of our business classes and interceptors to be defined as singleton instances. Those finer details must be handled in an actual implementation. In Approach 3, you will see how these particulars can be achieved to some extent.

The output of Approach 1's program matches the output of our static decorator. But here we have greater flexibility. Since MyProxyFactory and GenericInvocationHandler are generic, we can use these to wrap any business object. Of course, our business object must implement some predefined interface, which is a good programming practice.

The MyDebugInterceptor and MyAnotherInterceptor can also be generic and don't need to be separately written for each business interface and its methods. Suppose we want to do a security check before executing a business method; we can do it in an interceptor, within the interceptBefore() method. The security interceptor can read a configuration file/database and afterwards perform the security check. Let's say we have a custom security XML file like the following:

 

<security> <businessClass> <classname>MyBusinessObject<classname> <methodPermission>

<method>doExecute</method> <validRoles>admin,manager</validRoles> </methodPermission> </businessClass> </security>

This configuration can be read and cached. Later the security interceptor can do the processing accordingly by checking the name of the method and matching it with the role. (Note: You may be wondering how we can get the user's role in the interceptor, as we are not passing the role. That can be handled using ThreadLocal, but that is a separate topic.)

So inside our security interceptor's interceptBefore() method, the code resembles the following snippet:

 public Object interceptBefore(Object proxy, Method method, 
      Object[] args, Object realtarget) {
      
   String nameOfMethod = method.getName();
   String targetClassName = realtarget.getClass().getName();
   MethodPermissions mPerm = 
      SecurityFactory.getPermission(targetClassName);
   If ( !mPerm.isAuthorized(MyThreadLocalRoleStore.getRole(), 
                           nameOfMethod ) ) {
      throw new RuntimeException("User not authorized");
   }
   return null;
}

We still have to write MethodPermissions, SecurityFactory, MyThreadLocalRoleStore, etc., to implement the overall flow, but that extends beyond this discussion's scope. Also, the XML shown is for example purposes only; for a real implementation, more robust code (e.g., robust exception handling) is required. What happens if we have overloaded methods in our business object? The XML as well as the code inside interceptBefore() needs to be changed and other classes like MethodPermissions and SecurityFactory need to deal with those changes in detail.

Approach 1's class diagram appears in Figure 2.

Figure 2. Chain dynamic proxies: Approach 1 class diagram

Note: For simplicity and size constraints, methods and method parameters of some of the classes and interfaces in Figures 2 through 4 are not shown in detail.

In the above class diagram, note that the business interface (IMyBusinessObject) is not any way related with any other class or interface except the business object itself.

Dynamically chain a dynamic proxy: Approach 2

Approach 2 deals with the same set of classes as Approach 1, and from a client-call perspective, is also the same. The change proposed is in GenericInvocationHandler and in MyProxyFactory. As pointed out earlier in Approach 1, as the number of interceptors increases, the instances of GenericInvocationHandler and proxy instances also increase. Approach 2 tries to resolve this problem and achieve nearly the same benefit as Approach 1.

So the client code is the same as that in Approach 1:

1 2 3 4 Page 2
Page 2 of 4