代理模式:

为其他对象提供一个代理以控制对某个对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。

组成:

  • Subject:proxy 和 RealSubject 的公共对外方法
  • RealSubject:真实实现逻辑的类,对Client不可见
  • Proxy:用来代理和封装 RealSubject
  • Client:使用 Subject 和 Proxy 完成工作

使用举例

1. 延迟加载

假设有个功能组件,初始化它十分耗费资源。实际情况中,如果该组件未被实际使用,则该初始化属于资源浪费。因此,我们可以考虑如果当前并没有使用这个组件,则不需要真正地初始化它,先使用一个代理对象替代它的原有的位置,只要在真正需要的时候才对它进行加载。

下面用一个数据库查询的操作作为例子(假设查询前需要连接数据库比较耗时)

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
// Subject : 对外提供查询方法
public interface IDBQuery {
String request();
}
//RealSubject
public class DBQuery implements IDBQuery {
// 初始化
public DBQuery() {
try {
Thread.sleep(1000);//假设数据库连接等耗时操作
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
//查询方法
@Override
public String request() {
return "request string";
}
}
//Proxy: 代理类,包含对RealSubject的引用
public class DBQueryProxy implements IDBQuery {
private DBQuery real = null;
@Override
public String request() {
//在真正需要的时候才能创建真实对象,创建过程可能很慢
if (real == null) {
real = new DBQuery();
}
return real.request();
}
}
//Client:使用 Subject 和 Proxy 完成工作
public class Main {
public static void main(String[] args) {
IDBQuery q = new DBQueryProxy(); //使用代理
q.request(); //在真正使用时才创建真实对象
}
}
2. 方法执行前后增加额外操作

类似上述数据库查询的例子,要求记录每次request的操作时间。(这个例子不考虑加载慢的问题)

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
// Subject : 对外提供查询方法
public interface IDBQuery {
String request();
}
//RealSubject
public class DBQuery implements IDBQuery {
//查询方法
@Override
public String request() {
return "request string";
}
}
//Proxy: 代理类,包含对RealSubject的引用
public class DBQueryProxy implements IDBQuery {
private DBQuery real = new DBQuery();
@Override
public String request() {
long begin = System.currentTimeMillis();
String request = real.request();
System.out.println("executed in " + (System.currentTimeMillis() - begin) + " msec");
return request;
}
}
//Client:使用 Subject 和 Proxy 完成工作
public class Main {
public static void main(String[] args) {
IDBQuery q = new DBQueryProxy(); //使用代理
q.request();
}
}

上述的两个例子都属于静态代理,静态代理也是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。(因为我们编写了固定的类来实现代理)

动态代理

动态代理是指在运行时动态生成代理类。和静态代理相比,动态代理不需要为 RealSubject 写一个形式完全一样的封装类(Proxy),方便维护。其次,使用一些动态代理的生成方法甚至可以在运行时制定代理类的执行逻辑,从而大大提升系统的灵活性。
动态代理使用字节码动态生成加载技术,方法有很多。这里介绍下 JDK自带的动态代理,CGLIB。

JDK自带的动态代理

JDK 的动态代理使用简单,它内置在 JDK 中,因此不需要引入第三方 Jar 包,但相对功能比较弱,只能针对实现了接口的类生成代理。

以下使用 core java 中的打印执行方法的例子:

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 TestInvocationHandler {
/**
* 提供给代理对象的调用处理器,是实现了 InvocationHandler接口的类对象。
* 调用代理对象的方法时,调用处理器的 invoke方法会被调用
* 根据传入的Method和args来执行RealSubject的对应方法
*/
static class TraceHandler implements InvocationHandler {
public TraceHandler(Object o) {
target = o;
}
/**
* 该方法负责集中处理动态代理类上的所有方法调用
* @param proxy : 代理类实例
* @param m : 被调用的方法对象
* @param args : 调用参数
* 调用处理器根据这三个参数进行预处理或分派到委托类实例上反射执行
*/
public Object invoke(Object proxy, Method m, Object[] args)
throws InvocationTargetException, IllegalAccessException {
System.out.print(target);
System.out.print("." + m.getName() + "(");
if (args != null) {
for (int i = 0; i < args.length; i++) {
System.out.print(args[i]);
if (i < args.length - 1) {
System.out.print(", ");
}
}
}
System.out.println(")");
return m.invoke(target, args);
}
private Object target;
}
public static void main(String[] args) {
Object[] elements = new Object[1000];
for (int i = 0; i < elements.length; i++) {
Integer value = i + 1;
InvocationHandler handler = new TraceHandler(value);
/**
* 使用newProxyInstance创建一个代理对象
* 传入参数:
* 1.类加载器(null表示使用默认的)
* 2.每个元素都需要实现的接口(该例子使用了二分查找,二分查找需要使用compareTo方法,所以所有元素都需要实现 Comparable接口)
* 3.调用处理器
* 代理对象具有接口所需要的方法 + Object类的全部方法
*/
Object proxy = Proxy.newProxyInstance(null, new Class[] { Comparable.class }, handler);
elements[i] = proxy;
}
Integer key = new Random().nextInt(elements.length) + 1;
//在查找时,调用元素的 compareTo方法时,首先是调用了 proxy的invoke方法,然后在invoke方法内部再去调用RealSubject的方法。
int result = Arrays.binarySearch(elements, key);
if (result >= 0) {
System.out.println(elements[result]);
}
}
}

CGLIB

CGLIB 和 Javassist 都是高级的字节码生成库,总体性能比 JDK 自带的动态代理好,而且功能十分强大。
(CGLIB 是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的所有方法,所以该类或方法不能声明成final的。)

使用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
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class TestCglib {
// RealSubject
static class DBQuery {
public void request() {
System.out.println("request string");
}
}
static class RequestProxyLib implements MethodInterceptor {
private Object target; // CGLib需要代理的目标对象
/**
* 创建代理对象
*/
public Object getInstance(Object target) {
this.target = target;
//Enhancer可以用来动态的生成一个类,这个类可以继承指定的一个类,实现指定的一些接口。
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(this.target.getClass());
// 回调方法
enhancer.setCallback(this);
// 创建代理对象
return enhancer.create();
}
@Override
// 回调方法,用途类似jkd代理中的invoke方法
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
long begin = System.currentTimeMillis();
proxy.invokeSuper(obj, args);
System.out.println("executed in " + (System.currentTimeMillis() - begin) + " msec");
return null;
}
}
public static void main(String[] args) {
RequestProxyLib cglib = new RequestProxyLib();
DBQuery bookCglib = (DBQuery) cglib.getInstance(new DBQuery());
bookCglib.request();
}
}

参考:

Proxy
Java 动态代理机制分析及扩展
代理模式原理及实例讲解
Java设计模式——代理模式实现及原理