Sunday, December 9, 2007

Delegate using Dynamic Proxy

Java had the Dynamic proxy since 1.3. Somehow it is not a well known feature, maybe because the documentation is not so good. Of course there are some good articles written by Brian. Anyway I am going to show you how you can use Dynamic proxy to delegate easily.
First lets imagine you want to use the features of an existing class, but keeping good OO practices in mind, you decide to use delegation. Now the problem is you need to add lot of methods just for delegating to the actual class.
For eg, lets say you want to use the functionality of a List. You define your class and delegate to the actual List. It may look like this:

public class MyList implements List {
private List list;

public MyList() {
this.list = new ArrayList();
}

public void add(Object item) {
list.add(item);
}
//Similarly override all methods
}

This is not particularly interesting. Of course the IDEs help, but if the interface changes you need to add,recompile etc.

Enter Dynamic Proxy.

Simply put, there are two things you need to do to implement dynamic proxy. Create a Proxy (this sits between the user and your object. for eg. MyList) and create an InvocationHandler (This guy will intercept all methods and allows you to override at runtime).
Before we begin, the proxy creation method is not pretty so lets have a utility method that does that for us:

public static <T> T getProxy(Class<T> clazz, Object obj) {
    return (T) Proxy.newProxyInstance(clazz.getClassLoader(),
            new Class[] { clazz },
            new Handler(obj));
    }

Now we will write the Handler (which is our InvocationHandler):

class Handler implements InvocationHandler {
    private Object obj;

    public Handler(Object obj) {
        this.obj = obj;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String methodName = method.getName();
        try {
//Try the real object first (eg. MyList)
            Method m = obj.getClass().getMethod(methodName, method.getParameterTypes());
            return m.invoke(obj, args);
        } catch (NoSuchMethodException e) {
        }
        try {
//That failed. So try the underlying object (eg. ArrayList)
            Method m = method.getDeclaringClass().getMethod(methodName, method.getParameterTypes());
            Object delegator = obj.getClass().getMethod("getUnderlying").invoke(obj);
            return delegator.getClass().getMethod(methodName, method.getParameterTypes()).invoke(delegator, args);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        return null;
    }
}

Hmm, so what's going on here?
We intercept all calls to MyList and check whether it is implemented in MyList. If not, we will get the underlying object (For this we assume a method 'getUnderlying' is defined on the main class. Ideally this will be passed as argument) and invoke that. Simple isn't it?

Finally we don't even have to implement List. Our class will look like:

public class MyList {
    private List list;

    public MyList() {
        this.list = new ArrayList();
    }

//The methods specific to this class..
    public void addItem(Object item) {
        list.add(item);
    }

//Get the delegated object
    public Object getUnderlying() {
        return list;
    }
}

Ok, all that looks good, but how to use it?

List list = ListProxy.getProxy(List.class, new MyList());
list.add("a"); //add method of ArrayList
list.addItem("b"); //addItem method of MyList
println(list.size()); //size method of ArrayList -> prints 2

What have we done?
1. We used delegation without the pain of overriding all methods which have default behavior.
2. We used Duck typing (Though MyList does not implement List, we can use it as a List).
3. This code is generic and can be used for any interface/class delegation.

Thats all for now, folks!

3 comments:

Anonymous said...

The code you wrote that invokes the proxy with a method not in the List interface didn't look right, so I actually tried to build your code. As I thought, Java thinks addItem is an undefined symbol, because it is not defined in the List interface.

If you were actually able to run this code, either I didn't implement what you wrote correctly, or else you've made a mistake or left something out.

Any ideas?

bodhi said...

oops, Not sure how I missed this. There should be another interface "List" (or some name) which extends "java.util.List" and adds the "addItem" method. And we work on that.
Other option is to invoke the methods using reflection, but that is not pretty.

Anonymous said...

Not a problem. I just wanted to be sure I wasn't going crazy. :-)

Your two options are along the lines of what I thought I would have to do.

Thanks for the prompt response.