spring recipes读书笔记--理解AOP原理
引入一个简单的数值计算器的例子,首先创建计算器接口,然后提供一个简单的实现类,在实现类中,需要对传入参数进行验证,并且进行日志记录。
package com.aop.example;public interface ArithmeticCalculator {public double add(double a,double b);public double sub(double a,double b);}package com.aop.example;import com.sun.org.apache.commons.logging.Log;import com.sun.org.apache.commons.logging.LogFactory;public class ArithmeticCalculatorImpl implements ArithmeticCalculator {private Log log=LogFactory.getLog(this.getClass());public double add(double a, double b) {// TODO Auto-generated method stublog.info("the method add() begins with "+a+","+b);validate(a);validate(b);double result = a+b;System.out.println(a+"+"+b+" = "+result);log.info("the method add() ends with "+result);return result;}public double sub(double a, double b) {// TODO Auto-generated method stublog.info("the method add() begins with "+a+","+b);validate(a);validate(b);double result = a-b;System.out.println(a+"-"+b+" = "+result);log.info("the method add() ends with "+result);return result;}private void validate(double a) {// TODO Auto-generated method stubif(a<0){throw new IllegalArgumentException("Illegal argument");}}}
上述代码中,日志和验证属于非业务需求,将这类代码写入业务逻辑中,耦合性增大,并且带来了很多的重复性代码。如果日志和验证需求改变,必须修改所有模块。
计算器应该只关心核心计算逻辑,需要将日志和验证关注点从其中分离出来。
采用动态代理的方法,建立InvocationHandler接口,并提供日志和验证实现类。
package com.aop.example;public class ArithmeticCalculatorImpl implements ArithmeticCalculator {public double add(double a, double b) {// TODO Auto-generated method stubdouble result = a+b;System.out.println(a+"+"+b+" = "+result);return result;}public double sub(double a, double b) {// TODO Auto-generated method stubdouble result = a-b;System.out.println(a+"-"+b+" = "+result);return result;}}package com.aop.example;import java.lang.reflect.Method;public interface InvocationHandler {public Object invoke(Object proxy,Method method,Object[] args) throws Throwable;}package com.aop.example;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.lang.reflect.Proxy;import java.util.Arrays;import com.sun.org.apache.commons.logging.Log;import com.sun.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 {// TODO Auto-generated method stublog.info("the method "+ method.getName()+" begins with "+Arrays.toString(args));Object result=method.invoke(target, args);log.info("the method "+method.getName()+" ends with "+result);return result;}public static Object createProxy(Object target){return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new CalculatorLoggingHandler(target));}}package com.aop.example;import java.lang.reflect.Method;import java.lang.reflect.Proxy;public class CalculatorValidationHandler implements InvocationHandler {private Object target;public CalculatorValidationHandler(Object target) {super();this.target = target;}public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {// TODO Auto-generated method stubfor(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("illegal argument");}}public static Object createProxy(Object target){return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new CalculatorLoggingHandler(target));}}
最后做一下测试
package com.aop.example;public class AopTest {public static void main(String[] args){ArithmeticCalculator arithmeticCalculatorImpl= new ArithmeticCalculatorImpl();ArithmeticCalculator arithmeticCalculator = (ArithmeticCalculator) CalculatorValidationHandler.createProxy(CalculatorLoggingHandler.createProxy(arithmeticCalculatorImpl));arithmeticCalculator.add(1.0, 2.0);arithmeticCalculator.add(-2.2, 2.0);arithmeticCalculator.sub(3.0, 2.0);}}
这里使用验证代理包装日志代理形成了一个代理链。所有计算器的调用首先经过验证代理,然后再是日志代理。