Lightweight AOP with plain Java SE 1.3+, without additional libraries, frameworks and tools 📎
Although AOP separates the infrastructure from the business and so makes the code more maintainable, it introduces also some additional complexity. AOP relies on (static/dynamic) bytecode manipulation often with own classloaders or java agents. This can be funny in managed environments like RCP-Frameworks or Java EE containers :-).
Also some additional libraries have also be shipped, which introduces some risk in the deployment.
Interestingly enough: Java has already a built-in a simple interception mechanism called: dynamic proxy. It is actually very old (since JDK 1.3+), so it seems to be almost forgotten.
The key element is one simple interface from the package java.lang.reflect, called InvocationHandler. It comes with a simplistic method:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
This Handler-interface has to be realized by a custom class which in fact already represents the cross cutting aspect. In our case it is a simple logger:
public class TracingInvocationHandler implements InvocationHandler{
public Object invoke(Object proxy, Method method, Object parameters[])
throws Throwable {
Object retVal;
Method targetMethod = null;
try {
long start = System.currentTimeMillis();
System.out.println("Invoking: " + formatMethodOutput(method,parameters));
targetMethod = getMethodForTarget(method,method.getParameterTypes());
retVal = targetMethod.invoke(target, parameters);
//performance mesurement
} catch (InvocationTargetException e) {
//Exception reporting
e.getTargetException().toString());
//rethrowing the exception
throw e.getTargetException();
} catch (Exception e) {
throw new RuntimeException("unexpected invocation exception: " + e.getMessage());
}
return retVal;
}
Now we have to somehow inject this additional behavior to the existing Java-code. This can be done with the static method java.lang.reflect.Proxy.newProxyInstance.
And now some limitation of this approach are visible:
- The Proxy-class is only able to decorate interfaces
- It is hard to inject the additional behavior to existing spaghetti code...
Map map =(Map) Decorator.trace(new HashMap(),Map.class);
System.out.println("Map: " +map.getClass().getName());
map.put("hallo","world");
map.get("hallo");
map.size();
The execution of this piece of code creates the following output:
Map: $Proxy0
Invoking: java.lang.Object java.util.Map.put(java.lang.String hallo,java.lang.String world)
put returns: null performed in: 0 ms!
Invoking: java.lang.Object java.util.Map.get(java.lang.String hallo)
get returns: world performed in: 0 ms!
Invoking: int java.util.Map.size()
size returns: 1 performed in: 0 ms!
You will find the whole, fully functional, project in p4j5 (Check-Out the DynamicProxy NB-project and "run" it).