Thursday, August 16, 2007

Make use of Java dynamic proxy

I didn't know about Java dynamic proxy before but finally got a chance to learn and found a good use for it. The test I was writing had the smell of repeating code:

private void testPutWithoutSynch(params...) throws Exception {
...

map.put("k1", "v1");

assertions...
}

private void testPutWithSynch(params...) throws Exception {
...

synchronized(map) {
map.put("k1", "v1");
}

assertions...
}


The test pattern repeated with other operations that you can do with Map like puttAll, remove, etc... Do_set_up() is actually some pre setup steps that I need to perform according to parameters list. As you can see, I have to repeat the test method twice for each Map operation I want to test, with and without the synchronize block.

So there is this code smell and I didn't know get rid of it. Then Tim Eck, my coworker, showed me how to use Java proxy. The idea is to wrap my map in a proxy, and use reflection to invoke methods of the map. By using a invocation handler, I can have a variable to control whether or not I want to use synchronize for that particular operation.

class Handler implements InvocationHandler {
private final Map map;
private boolean useSynch = false;

public Handler(Map map) {
this.map = map;
}

public void setUseSynch(boolean flag) {
this.useSynch = flag;
}

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (useSynch) {
return invokeWithSynch(method, args);
} else {
return method.invoke(map, args);
}
} catch (InvocationTargetException e) {
throw e.getTargetException();
}
}

private Object invokeWithSynch(Method method, Object[] args) throws Throwable {
synchronized (map) {
return method.invoke(map, args);
}
}
}


Then the proxy can be created like this:

Map map = new HashMap();
Handler handler = new Handler(map);
Map mapProxy = (Map)Proxy.newProxyInstance(getClass().getClassLoader(), new Class[] { Map.class }, handler);


With that my test methods can be reduced to:

private void testPut(params..., boolean useSynch) throws Exception {
do_set_up(params)

handler.setUseSynch(useSynch);
mapProxy.put("k1", "v1");

assertions depends on useSynch
}


Then I just need to call this method twice and accomplish the same thing.

testPut(params..., false);
testPut(params..., true);


Pretty neat huh? Java proxy is nice tool to know.
Post a Comment