Problem
As non-modularized crosscutting concerns will cause the code tangling and code scattering problems, you would like to seek a method to modularize them. However, they are often hard to modularize with the traditional object-oriented approach, as they span multiple modules of an application.
Solution
You can apply a design pattern called proxy to separate crosscutting concerns from core concerns. Proxy is one of the 23 GoF (Gang of Four) object-oriented design patterns, which belong to the “structural pattern” category. The principle of the proxy design pattern is to wrap an object with a proxy and use this proxy to substitute for the original object. Any calls that were made to the original object will go through the proxy first. Figure 5-2 illustrates the general idea of the proxy design pattern.
Figure 5-2. General idea of the proxy design pattern
The proxy object is responsible for deciding when and whether to forward method calls to the original object. In the meanwhile, the proxy can also perform additional tasks around each method call. So, the proxy would be a good place to implement the crosscutting concerns. In Java, there are two ways to implement the proxy design pattern. The traditional one is to write a static proxy in pure object-oriented style. Static proxy works by wrapping an object with a dedicated proxy to performadditional tasks around each method call. The dedication means you have to write a proxy class for each interface to be able to substitute for the original implementation, which is very inefficient in a large application with hundreds or thousands of components.
Another method is through the dynamic proxy support offered by JDK version 1.3 or higher. It supports creating a proxy dynamically for any object. The only restriction is that the object must implement at least one interface, and only the calls to the methods declared in the interfaces will go through the proxy. However, there’s another kind of proxy, CGLIB proxy, that doesn’t have this restriction. It can handle all the methods declared in a class even if it doesn’t implement any interface. Dynamic proxies are implemented with the Java Reflection API, so they can be used in a more general way than static proxies. For this reason, dynamic proxy is one of the core technologies used by Spring for its AOP implementation.
How It Works
A JDK dynamic proxy requires an invocation handler to handle method invocations. An invocation handler is simply a class that implements the following InvocationHandler interface:
package java.lang.reflect; public interface InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable; }
The only method declared in this interface is invoke(). It allows you to control the overall invocation process yourself. The first argument of the invoke() method is the proxy instance whose method was being invoked. The second argument is the method object whose type is java.lang.reflect.Method. It represents the current method being invoked. The last argument is an array of arguments for invoking the target method. Finally, you have to return a value as the current method invocation’s result.
Creating the Logging Proxy
By implementing the InvocationHandler interface, you can write an invocation handler that logs the method beginning and ending. You require a target calculator object to perform the actual calculation, which is passed in as a constructor argument.
package com.calculator; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.util.Arrays; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; public class CalculatorLoggingHandler implements InvocationHandler { private Log log = LogFactory.getLog(this.getClass()); private Object target; public CalculatorLoggingHandler(Object target) { this.target = target; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // Log the method beginning with the method name and arguments.
log.info("The method " + method.getName() + "() begins with " + Arrays.toString(args)); // Perform the actual calculation on the target calculator object by calling // Method.invoke() and passing in the target object and method arguments. Object result = method.invoke(target, args); // Log the method ending with the returning result. log.info("The method " + method.getName() + "() ends with " + result); return result; } }
By using reflection, the invoke() method is general enough to handle all method calls of the two calculators. You can access the method name by calling Method.getName(), and you can access the arguments in an object array. To perform the actual calculation, you make a call to invoke() on the method object and pass in the target calculator object and method arguments. To create a JDK dynamic proxy instance with an invocation handler, you just make a call to the static method Proxy.newProxyInstance().
package com.calculator; import java.lang.reflect.Proxy; public class Main { public static void main(String[] args) { ArithmeticCalculator arithmeticCalculatorImpl = new ArithmeticCalculatorImpl(); ArithmeticCalculator arithmeticCalculator = (ArithmeticCalculator) Proxy.newProxyInstance( arithmeticCalculatorImpl.getClass().getClassLoader(), arithmeticCalculatorImpl.getClass().getInterfaces(), new CalculatorLoggingHandler(arithmeticCalculatorImpl)); … } }
The first argument of this method is the class loader to register this proxy. In most cases, you should define a proxy in the same class loader as the original class. The second argument consists of the interfaces for this proxy to implement. Only the calls to the methods declared in these interfaces will go through the proxy. Usually, you will proxy for all interfaces of the target class. The last argument is your invocation handler to handle the method invocation. By calling this method, you will get a proxy instance that was created by JDK dynamically. You can use it for calculation to have all method calls pass through the logging handler. For reuse purpose, you can encapsulate the proxy creation code in a static method of the handler class.
package com.calculator; … import java.lang.reflect.Proxy; public class CalculatorLoggingHandler implements InvocationHandler { … public static Object createProxy(Object target) { return Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), new CalculatorLoggingHandler(target)); } }
Now, the proxy creation code in the Main class is as simple as calling the static method.
package com.calculator; public class Main { public static void main(String[] args) { ArithmeticCalculator arithmeticCalculatorImpl = new ArithmeticCalculatorImpl(); ArithmeticCalculator arithmeticCalculator = (ArithmeticCalculator) CalculatorLoggingHandler.createProxy( arithmeticCalculatorImpl); … } }
With this general logging invocation handler, you can also create a proxy for UnitCalculator dynamically.
package com.calculator; public class Main { public static void main(String[] args) { … UnitCalculator unitCalculatorImpl = new UnitCalculatorImpl(); UnitCalculator unitCalculator = (UnitCalculator) CalculatorLoggingHandler.createProxy( unitCalculatorImpl); … } }
Figure 5-3 illustrates the implementation of the logging concern with the proxy design pattern.
Figure 5-3. Implementation of the logging concern with proxy
Creating the Validation Proxy
Similarly, you can write a validation handler as shown following. As a different method may have a different number of arguments, you have to loop the argument array to validate each method argument.
package com.calculator; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class CalculatorValidationHandler implements InvocationHandler { public static Object createProxy(Object target) { return Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), new CalculatorValidationHandler(target)); } private Object target; public CalculatorValidationHandler(Object target) { this.target = target; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { for (Object arg : args) { validate((Double) arg); } Object result = method.invoke(target, args); return result; } private void validate(double a) { if (a < 0) { throw new IllegalArgumentException("Positive numbers only"); } } }
In the Main class, you can wrap the logging proxy by a validation proxy to form a proxy chain. Any calculator method calls will go through the validation proxy first and then the logging proxy. That means validation will be performed prior to logging. If you prefer the reverse order, you should use the logging proxy to wrap the validation proxy instead.
package com.calculator; public class Main { public static void main(String[] args) { ArithmeticCalculator arithmeticCalculatorImpl = new ArithmeticCalculatorImpl(); ArithmeticCalculator arithmeticCalculator = (ArithmeticCalculator) CalculatorValidationHandler.createProxy( CalculatorLoggingHandler.createProxy( arithmeticCalculatorImpl)); … UnitCalculator unitCalculatorImpl = new UnitCalculatorImpl(); UnitCalculator unitCalculator = (UnitCalculator) CalculatorValidationHandler.createProxy( CalculatorLoggingHandler.createProxy( unitCalculatorImpl)); … } }
Figure 5-4 illustrates the implementation of both the validation and logging concerns with the proxy design pattern.
Figure 5-4. Implementation of both the validation and logging concerns with proxy