代理模式

代理模式的设计思想就是设置一个中间代理来控制访问原目标对象,以达到增强原对象的功能和简化访问方式。

为了保持行为的一致性,代理类和委托类通常会实现相同的接口,所以在访问者看来两者没有丝毫的区别。通过代理类这中间一层,能有效控制对委托类对象的直接访问,也可以很好地隐藏和保护委托类对象,同时也为实施不同控制策略预留了空间,从而在设计上获得了更大的灵活性。

按照代理的创建时期,代理类可以分为两种:

  1. 静态代理:由程序员创建代理类或特定工具自动生成源代码再对其编译。(代理类在程序编译前就已经存在)
  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
26
27
28
29
30
31
32
33
34
// 定义接口
abstract class Subject {
public abstract void Request();
}

// 接口的真实实现
class RealSubject extends Subject {
@Override
public void Request() {
// TODO: 真实的请求逻辑
}
}

// 接口的代理实现
class Proxy extends Subject {

private RealSubject real; // 保存要代理的真实对象

public ProxyHandler(Object subject) {
this.real = subject; // 代理的真实对象赋初值 (也可以根据需要,内部 new)
}

@Override
public void Request() {

// TODO: 增加额外操作
real.Request(); // 调用真实对象的接口
// TODO: 增加额外操作
}
}

//------------ 使用代理模式 ---------------
Subject subject = new Proxy(new RealSubject());
subject.Request();

静态代理特点

  • 优点:可以在不修改目标对象的前提下扩展目标对象的功能。
  • 缺点:1,冗余。由于代理对象要实现与目标对象一致的接口,会产生过多的代理类;2,不易维护。一旦接口增加方法,目标对象与代理对象都要进行修改。

JDK 动态代理

JDK 动态代理的思路:在运行状态中,需要代理的地方,根据 Subject 和 RealSubject,动态地创建一个 Proxy,用完之后,就会销毁,这样就可以避免了 Proxy 角色的 class 在系统中冗杂的问题了。

InvocationHandler 与 Proxy

JDK 实现动态代理机制,需要用到 java.lang.reflect.InvocationHandlerjava.lang.reflect.Proxy 类。

java.lang.reflect.InvocationHandler 接口只有一个方法 invoke:

1
2
3
4
5
6
/* 参数说明:
// proxy - 代理的真实对象。
// method - 所要调用真实对象的某个方法的 Method 对象
// args - 所要调用真实对象某个方法时接受的参数
*/
Object invoke(Object proxy, Method method, Object[] args) throws Throwable

java.lang.reflect.Proxy 用来动态创建一个代理对象的类,我们主要使用 newProxyInstance 这个方法:

1
2
3
4
5
6
/* 参数说明:
// loader - 加载代理对象的 ClassLoader,通常就是接口类的ClassLoader。
// interfaces - 需要实现的接口数组,通常就是接口类的interfaces。
// handler - InvocationHandler 对象实例
*/
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler) throws IllegalArgumentException

JDK 动态代理实现

实现步骤:

  1. 实现 InvocationHanlder 接口,并重写 invoke 方法;
  2. 创建 InvocationHanlder 实例;
  3. 调用 Proxy.newProxyInstance() 返回一个代理对象;
  4. 使用代理对象。
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
// 定义接口
abstract class Subject {
public abstract void Request();
}

// 接口的真实实现
class RealSubject extends Subject {
@Override
public void Request() {
// TODO: 真实的请求逻辑
}
}

// 实现 InvocationHanlder 接口
public class ProxyHandler implements InvocationHandler {

private Object real; // 保存要代理的真实对象

public ProxyHandler(Object subject) {
this.real = subject; // 代理的真实对象赋初值 (也可以根据需要,内部 new)
}

@Override
public Object invoke(Object object, Method method, Object[] args)
throws Throwable {

// TODO: 增加额外操作
Object obj = method.invoke(real, args); // 通过反射调用真实对象的方法
// TODO: 增加额外操作

return obj;
}
}

//------------ 使用代理模式 ---------------
ProxyHandler handler = new ProxyHandler(new RealSubject());

// 通过 Proxy.newProxyInstance 方法来创建我们的代理对象
Subject subject = (Subject)Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject.getClass().getInterfaces(), handler);

subject.Request();

通过 Proxy.newProxyInstance 创建的代理对象是在 jvm 运行时动态生成的一个对象,它并不是我们的 InvocationHandler 类型,也不是我们定义的那组接口的类型,而是在运行是动态生成的一个对象,其类名格式为 com.sun.proxy.$Proxy{num}。

JDK 动态代理特点

  • 优点:相对于静态代理模式,不需要硬编码接口,代码复用率高。
  • 缺点:只能代理接口。

CGLIB 动态代理

CGLIB(Code Generation Library)是一个基于 ASM 的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成。CGLIB 通过继承方式为没有实现接口的类提供代理。

CGLIB作为一个开源项目,其代码托管在github,地址为:https://github.com/cglib/cglib

CGLIB 动态代理实现

实现步骤:

  1. 实现 MethodInterceptor 接口,并重写 intercept 方法;
  2. 创建 MethodInterceptor 实例。
  3. 创建 Enhancer 实例,并通过 setSuperclass 设置代理类,通过 setCallback 设置 MethodInterceptor 实例。
  4. 通过 enhancer.create 创建代理对象。
  5. 使用代理对象。
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
// 接口的真实实现
public class RealSubject {

public void Request() {
// TODO: 真实的请求逻辑
}
}

// 实现 MethodInterceptor 接口
class ProxyMethodInterceptor implements MethodInterceptor {

@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {

// TODO: 增加额外操作
Object obj = proxy.invokeSuper(obj, args); // 通过反射调用真实对象的方法
// TODO: 增加额外操作

return obj;
}
}

//------------ 使用代理模式 ---------------
ProxyMethodInterceptor interceptor = new ProxyMethodInterceptor();

Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(RealSubject.class);
enhancer.setCallback(interceptor);

// 通过 enhancer.create() 方法来创建我们的代理对象
RealSubject subject = (RealSubject)enhancer.create();

// 调用方法
subject.Request();

CGLIB 动态代理特点

  • 优点:使用字节码增强,比 JDK 动态代理方式性能高。可以在运行时对类或者是接口进行增强操作,且委托类无需实现接口。
  • 缺点:不能对 final 类以及 final 方法进行代理。

参考文档

https://dunwu.github.io/javacore/basics/java-reflection.html#_4-2-jdk-%E5%8A%A8%E6%80%81%E4%BB%A3%E7%90%86
https://juejin.cn/post/6844903983761326093
https://www.cnblogs.com/54chensongxia/p/12502624.html
https://blog.csdn.net/luanlouis/article/details/24589193
https://www.runoob.com/w3cnote/cglibcode-generation-library-intro.html