引入

在之前的文章中我们介绍了常见的设计模式,今天我们来使用 Java 语言对代理模式进行具体实现举例。

原理

代理模式在不同语言的实现以及对应机制不太一样,所以在此对代理模式原理进行阐述。

实现目的:

代理模式意图提供一种代理以控制对这个对象的访问,在Spring当中的典型应用是AOP,在调用目标对象方法时,做前置、后置、异常等处理。

在Spring当中,代理模式的实现主要分为JDK动态代理和CGLIB动态代理。下面我们对他们分别来实验并比较。

被代理的接口及类

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
/**
* 学生
*
* @author marx
* @date 2022/03/15
*/
interface Student {
/**
* 研究
*/
void study();

/**
* 打个招呼
*/
default void sayHi() {
System.out.println("Hi");
}
}

/**
* 孩子
*
* @author marx
* @date 2022/03/15
*/
interface Child {
/**
* 玩
*/
void play();
}

/**
* 汤姆
*
* @author marx
* @date 2022/03/15
*/
public class Tom implements Student, Child{
/**
* 研究
*/
@Override
public void study() {
System.out.println("studying...");
}

/**
* 玩
*/
@Override
public void play() {
System.out.println("playing...");
}

/**
* 吃
*/
public void eat() {
System.out.println("eating...");
}
}

JDK动态代理实例

在下面的例子中,我们使用Java原生的JDK动态代理来生成代理对象,因为JDK动态代理是基于接口的,所以这里我们生成Student接口和Child接口的代理对象。

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
65
66
67
68
69
70
71
/**
* JDK动态代理
*
* @author marx
* @date 2022/03/15
*/
public class JDKAgent {
public static void main(String[] args) {
// 被代理对象
Student tom = new Tom();
// 目标处理Handle
InvocationHandler handler = new InvocationHandlerImpl(tom);
// 目标的类加载器
ClassLoader loader = tom.getClass().getClassLoader();
// 获得被代理对象的所有接口
Class[] interfaces = tom.getClass().getInterfaces();

// 对Student接口进行代理
Student agentStudent = (Student) Proxy.newProxyInstance(loader, interfaces, handler);
// 对Child接口进行代理
Child agentChild = (Child) Proxy.newProxyInstance(loader, interfaces, handler);


// 各个接口代理类分别测试
agentStudent.study();
agentStudent.sayHi();
agentChild.play();
}


/**
* 调用处理程序实现
*
* @author marx
* @date 2022/03/15
*/
static class InvocationHandlerImpl implements InvocationHandler {
/**
* 目标
*/
Object target;

public InvocationHandlerImpl(Object target) {
this.target = target;
}

/**
* 调用
*
* @param proxy 代理
* @param method 方法
* @param args arg游戏
* @return {@link Object}
* @throws Throwable throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 对method进行筛选来实现对指定方法代理
if (!"study".equals(method.getName())) {
System.out.println("Before agent...(except study)");
}

// 此处invoke方法的第一个参数不适用proxy对象是因为proxy对象是代理后对象,使用proxy对不断重复调用自身
Object obj = method.invoke(target, args);
System.out.println("After agent...");

System.out.println();
return obj;
}
}
}

CGLIB动态代理

在下面的例子中,我们使用CGLIB开源项目下的包来实现基于CGLIB的动态代理。因为CGLIB是基于继承实现的,所以我们在这里对Tom类来进行增强(也就是代理)。

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
/**
* cglibagent
*
* @author marx
* @date 2022/03/15
*/
public class CGLIBAgent {

public static void main(String[] args) {
// 增强器
Enhancer enhancer = new Enhancer();
// 指定代理类类型
enhancer.setSuperclass(Tom.class);
// 设置回调器,也就是对目标对象的代理器
enhancer.setCallback(new MyMethodInterceptor());
// 生成代理对象
Tom agentTom = (Tom) enhancer.create();

// 各种方法都被拦截了
agentTom.study();
agentTom.sayHi();
agentTom.play();
agentTom.eat();
}
}

/**
* 自定义方法拦截器
*
* @author marx
* @date 2022/03/15
*/
class MyMethodInterceptor implements MethodInterceptor {

/**
* 拦截
*
* @param o o
* @param method 方法
* @param objects 对象
* @param methodProxy 方法代理
* @return {@link Object}
* @throws Throwable throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
// 例如下,我们可以对method进行筛选来进行指定方法的增强
if (!"play".equals(method.getName())) {
System.out.println("before intercept...(except play)");
}

Object obj = methodProxy.invokeSuper(o, objects);

if (!"study".equals(method.getName())) {
System.out.println("after intercept...(except study)");
}

System.out.println();

return obj;
}
}

JDK动态代理和CGLIB动态代理的异同

  • JDK动态代理是java原生的,基于拦截器(必须实现InvocationHandler)及反射机制生成代理指定接口的匿名类,在使用具体的方法前会被InvokeHandler拦截处理
  • CGLIB是利用ASM框架将代理对象类生成的class文件加载进来,通过修改其字节码来生成子类
  • JDK动态代理只能对实现了接口的类生成代理,而不能针对类
  • Cglib是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法,并覆盖其中方法的增强,但是因为采用的是继承,所以该类或方法最好不要生成final,对于final类或方法,是无法继承的

参考

Cglib和jdk动态代理的区别