Java 动态代理

代理模式

参考:《JAVA 程序性能优化》

将代理类用于实现延迟加载,可以有效地提升系统的启动速度。

延迟加载的核心思想是:如果当前并没有使用这个组件,则不需要真正地初始化它,使用一个代理对象替代它的原有位置,只要在真正需要使用的时候,才对它进行加载。

代理模式的实现如下:

假设有一个接口 IDBQuery,它只有一个 request() 方法:

1
2
3
public interface IDBQuery {
String request();
}

DBQuery 的实现如下,它是一个重量级对象,构造会比较慢:

1
2
3
4
5
6
7
8
9
10
public class DBQuery implements IDBQuery {
public DBQuery() {
System.out.println("query...");
}

@Override
public String request() {
return "request string";
}
}

代理类 DBQueryProxy 是轻量级对象,创建很快,用于替代 DBQuery 的位置:

1
2
3
4
5
6
7
8
9
10
11
public class DBQueryProxy implements IDBQuery {
private DBQuery real = null;

@Override
public String request() {
if (real == null) {
real = new DBQuery();
}
return real.request();
}
}

最后,主函数如下,它引用 IDBQuery 接口,并使用代理类工作:

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
public class Main {
public static void main(String args[]) {
IDBQuery q = new DBQueryProxy();
q.request();
}
}
```

上面所说的静态代理模式,在实际开发中其实应用不大,因为他需要事先知道被代理对象是谁,而且被代理对象和代理对象实现了公共的接口。实际情况往往并不能满足这些条件,我们往往在写代理模式的时候并不知道到时候被代理的对象是谁。解决办法就是——动态代理。


## JDK 动态代理

> 参考:
>
> [Java 动态代理机制分析及扩展](https://www.ibm.com/developerworks/cn/java/j-lo-proxy1/index.html)
>
> [动态代理详解](https://www.cnblogs.com/fengqueryProxyngyue/p/6092151.html)

```
IDBQuery query = new DBQuery();
Class[] cs = {IDBQuery.class};
InvocationHandler h = new DBQueryInvocationHandler(query);

IDBQuery queryProxy = (IDBQuery) Proxy.newProxyInstance(IDBQuery.class.getClassLoader(), cs, h);
```

上面代码中,Proxy 类的静态方法 newProxyInstance() 方法生成了一个对象,这个对象实现了 cs 数组中指定的接口。没错,返回值 queryProxy 是 IDBQuery 接口的实现类。你不要问这个类是哪个类,你只需要知道 queryProxy 是 IDBQuery 接口的实现类就可以了。你现在也不用去管 loader 和 h 这两个参数是什么,你只需要知道,Proxy 类的静态方法 newProxyInstance() 方法返回的方法是实现了指定接口的实现类对象,甚至你都没有看见实现类的代码。
动态代理就是在运行时生成一个类,这个类会实现你指定的一组接口,而这个类没有 .java 文件,是在运行时生成的,你也不用去关心它是什么类型的,你只需要知道它实现了哪些接口即可。

### Proxy 类的 newProxyInstance() 方法
Proxy 类的 newInstance() 方法有三个参数:

- ClassLoader loader:它是类加载器类型,你不用去理睬它,你只需要知道怎么可以获得它就可以了:IDBQuery.class.getClassLoader() 就可以获取到 ClassLoader 对象,没错,只要你有一个 Class 对象就可以获取到 ClassLoader 对象。

- Class[] interfaces:指定 newProxyInstance() 方法返回的对象要实现哪些接口,没错,可以指定多个接口,例如上面例子我们只指定了一个接口:Class[] cs = {IDBQuery.class}。

- InvocationHandler h:它是最重要的一个参数,它是一个接口,它的名字叫调用处理器,无论你调用代理对象的什么方法,它都是在调用 InvocationHandler 的 invoke() 方法。

```
public class MyInvocationHandler implements InvocationHandler {

/**
* 将要被代理的对象
*/
private Object target;

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

/**
* @param proxy Proxy.newProxyInstance() 方法返回的代理对象
* @param method
* @param args
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("proxy class name: " + proxy.getClass().getName());
System.out.println("proxy class simple name: " + proxy.getClass().getSimpleName());

long startTime = System.currentTimequeryProxyllis();
Object o = method.invoke(target, args);
System.out.println("请求用时:" + (System.currentTimequeryProxyllis() - startTime) / 1000);
return o;
}

}
public static void main(String[] args) {
   IDBQuery query = new DBQuery();
   Class[] cs = {IDBQuery.class};
   InvocationHandler h = new DBQueryInvocationHandler(query);

   // JDK 动态代理,JDK 动态代理的类必须实现一个接口,而且生成的代理类是其接口的实现类,也就是被代理的类的兄弟类,由 JDK 内部实现
   IDBQuery queryProxy = (IDBQuery) Proxy.newProxyInstance(IDBQuery.class.getClassLoader(), cs, h);

   System.out.println("proxy class name: " + queryProxy.getClass().getName());
   System.out.println("proxy class simple name: " + queryProxy.getClass().getSimpleName());
   System.out.println(queryProxy.request());
}

InvocationHandler 接口只有一个方法,即 invoke() 方法!它是对代理对象所有方法的唯一实现。也就是说,无论你调用代理对象上的哪个方法,其实都是在调用 InvocationHandler 的 invoke() 方法。

InvocationHandler 的 invoke() 方法

InvocationHandler 的 invoke() 方法的参数有三个:

  • Object proxy:代理对象,也就是 Proxy.newProxyInstance() 方法返回的对象,通常我们用不上它。

    具体查看:
    Understanding “proxy” arguments of the invoke method of java.lang.reflect.InvocationHandler

  • Method method:表示当前被调用方法的反射对象,例如 queryProxy.request(),那么 method 就是 request() 方法的反射对象。

  • Object[] args:表示当前被调用方法的参数,当然 queryProxy.request() 这个调用是没有参数的,所以 args 是一个零长数组。

最后要说的是 invoke() 方法的返回值为 Object 类型,它表示当前被调用的方法的返回值,当然 queryProxy.request() 方法是没有返回值的,所以 invoke() 返回的就必须是 null 了。

CGLIB 动态代理

分享到:
Disqus 加载中...

如果长时间无法加载,请针对 disq.us | disquscdn.com | disqus.com 启用代理