Spring之AOP前奏
1. 提出问题
情景:数学计算器
要求:
- 执行加减乘除运算
- 日志:在程序执行期间追踪正在发生的活动
- 验证:希望计算器只能处理正数的运算
常规实现
Calculator
1 2 3 4 5 6 7 8 9 10 11 12
|
public interface Calculator { int add(int m,int n); int sub(int m,int n); }
|
CalculatorImpl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
public class CalculatorImpl implements Calculator {
public int add(int m, int n) { System.out.println("日志:执行了add方法参数1="+m+",参数2="+n); int result = m + n; System.out.println("日志:执行add方法结束,结果为"+result); return result; } public int sub(int m, int n) { System.out.println("日志:执行了sub方法参数1="+m+",参数2="+n); int result = m - n; System.out.println("日志:执行sub方法结束,结果为"+result); return result; } }
|
2. 问题分析
代码混乱
:越来越多的非业务需求(日志和验证等)加入后,原有的业务方法急剧膨胀。每个方法在处理核心逻辑的同时还必须兼顾其他多个关注点。
代码分散
: 以日志需求为例,只是为了满足这个单一需求,就不得不在多个模块(方法)里多次重复相同的日志代码。如果日志需求发生变化,必须修改所有模块。
3. 动态代理
原理:
- 代理设计模式的原理:使用一个代理将原本对象包装起来,然后用该代理对象”取代”原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。
方式:
- 基于接口实现动态代理: JDK动态代理
- 基于继承实现动态代理:
Cglib
、Javassist
动态代理
4. 动态代理改进后
注释,之前在CalculatorImpl中的日志信息。
4.1 创建代理对象
newProxyInstance(ClassLoader loader, 类<?>[] interfaces, InvocationHandler h)
返回指定接口的代理类的实例,该接口将方法调用分派给指定的调用处理程序。
- 内部类中使用Calculator,需要是final定义。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
|
public class CalculatorProxy {
public static Calculator getProxy(final Calculator calculator){
InvocationHandler h = new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null; try { LogUtils.logStart(method,args);
result = method.invoke(calculator, args);
LogUtils.logEnd(method,result); } catch (Exception e) { LogUtils.logError(method,e); e.printStackTrace(); }finally { LogUtils.logFinally(method,result); } return result; } };
Class<?>[] interfaces = calculator.getClass().getInterfaces(); ClassLoader loader = calculator.getClass().getClassLoader();
Calculator cal = (Calculator) Proxy.newProxyInstance(loader, interfaces, h); return cal; } }
|
4.2 创建日志工具类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
|
public class LogUtils {
public static void logStart(Method method,Object args){ System.out.println("["+method.getName()+"]:方法执行了,其参数列表为"+ Arrays.asList(args));
} public static void logEnd(Method method,Object result){ System.out.println("["+method.getName()+"]:方法正常执行完成了,其结果为"+ result); } public static void logError(Method method,Exception e){ System.out.println("["+method.getName()+"]:方法执行出现了异常,原因为:"+ e.getCause()); } public static void logFinally(Method method,Object result){ System.out.println("["+method.getName()+"]:方法最终执行完成了,其结果为"+ result); }
}
|
4.3 测试动态代理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
public class TestCalculator {
@Test public void testCalculator() { Calculator calculator = new CalculatorImpl(); int addRes = calculator.add(2, 3);
Calculator proxy = CalculatorProxy.getProxy(calculator); System.out.println("proxy.getClass() = " + proxy.getClass()); int add = proxy.add(5,6); System.out.println("测试结果 result = " + add); } }
|
5.使用动态代理实现存在的问题
- 写起来很复杂,实现复杂
- jdk默认的动态代理,如果目标对象没有实现任何接口,是无法为他创建代理对象的。