Spring之AOP前奏

1. 提出问题

情景:数学计算器
要求:

  1. 执行加减乘除运算
  2. 日志:在程序执行期间追踪正在发生的活动
  3. 验证:希望计算器只能处理正数的运算
常规实现

Calculator

1
2
3
4
5
6
7
8
9
10
11
12
 /**
* @Date 2020/5/23 23:23
* @Version 10.21
* @Author DuanChaojie
*/
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
/**
* @Date 2020/5/23 23:24
* @Version 10.21
* @Author DuanChaojie
*/
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. 问题分析

  1. 代码混乱:越来越多的非业务需求(日志和验证等)加入后,原有的业务方法急剧膨胀。每个方法在处理核心逻辑的同时还必须兼顾其他多个关注点。
  2. 代码分散: 以日志需求为例,只是为了满足这个单一需求,就不得不在多个模块(方法)里多次重复相同的日志代码。如果日志需求发生变化,必须修改所有模块。

3. 动态代理

原理:
  1. 代理设计模式的原理:使用一个代理将原本对象包装起来,然后用该代理对象”取代”原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。

img

方式:
  1. 基于接口实现动态代理: JDK动态代理
  2. 基于继承实现动态代理: CglibJavassist动态代理

4. 动态代理改进后

注释,之前在CalculatorImpl中的日志信息。

4.1 创建代理对象
  1. newProxyInstance(ClassLoader loader, 类<?>[] interfaces, InvocationHandler h)返回指定接口的代理类的实例,该接口将方法调用分派给指定的调用处理程序。
  2. 内部类中使用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
/**
* @Date 2020/5/24 9:50
* @Version 10.21
* @Author DuanChaojie
* // java.lang.reflect.Proxy
* // newProxyInstance(ClassLoader loader, 类<?>[] interfaces, InvocationHandler h)
* // 返回指定接口的代理类的实例,该接口将方法调用分派给指定的调用处理程序。
*/
public class CalculatorProxy {

/**
* 帮calculator对象创建代理对象
* @param calculator 被代理的对象
* @return
*/
public static Calculator getProxy(final Calculator calculator){

InvocationHandler h = new InvocationHandler() {
/**
* 方法执行器,帮我们被代理对象执行目标方法
* @param proxy 代理对象给jdk使用的,我们任何时候都不要使用这个对象
* @param method 当前将要执行的目标对象
* @param args 这个方法调用时外界传入的参数值
* @return
* @throws Throwable
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

//System.out.println("这是动态代理帮你执行的方法");
// 利用反射执行目标方法
// 内部类中使用,需要是final定义的
Object result = null;
try {
LogUtils.logStart(method,args);

// int a = 1/0; 异常的测试

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();

// Proxy为目标对象创建代理对象
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
/**
* @Date 2020/5/24 10:51
* @Version 10.21
* @Author DuanChaojie
*/
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
/**
* @Date 2020/5/24 10:39
* @Version 10.21
* @Author DuanChaojie
*/
public class TestCalculator {

@Test
public void testCalculator() {
Calculator calculator = new CalculatorImpl();
// 没使用动态代理之前
int addRes = calculator.add(2, 3);

// 使用动态代理,如果拿到这个对象的代理对象,代理对象执行加减乘除
Calculator proxy = CalculatorProxy.getProxy(calculator);

// proxy.getClass() = class com.sun.proxy.$Proxy4
// 代理对象和被代理对象唯一能产生的关系就是实现了同一个接口
System.out.println("proxy.getClass() = " + proxy.getClass());
int add = proxy.add(5,6);
System.out.println("测试结果 result = " + add);
}
}

5.使用动态代理实现存在的问题

  1. 写起来很复杂,实现复杂
  2. jdk默认的动态代理,如果目标对象没有实现任何接口,是无法为他创建代理对象的。