赵玉伟的博客

java动态代理剖析

概述

提起代理,可能大家都不陌生,代理模式是常用的设计模式之一。概念上讲,客户端可以通过代理对象访问真正的对象。更进一步,当我们说到java的动态代理时,只需要Proxy, InvocationHandler 两个类便可以实现一个动态代理框架,这得益于java对动态代理封装的很好,提供了一个很简便的黑盒入口。 但是,动态代理的实现远比字面的概念要复杂,而且,很多基本的框架,比如Spring的AOP, RPC调用等常用的技术,底层都依赖动态代理。可惜的是由于我们平时忙于业务实现,使用的机会不是很多,导致容易把动态代理的实现忽略。本文重点分析动态代理,包括代理对象的生成,调用的流程。

代理的角色:

有三个角色,
客户端, client
代理对象, proxy
被代理对象,target
以下会用三个单词代指各个角色。proxy介于client与target之间。

动态代理、静态代理的字面理解

从动静的维度分, 可以分为静态代理,与动态代理, 那么,这里的静态、动态具体代表什么含义?
动态、静态指的是代理对象proxy。 静态代理,指的是代理对象在编译期生成。动态代理,指的是代理对象在编译期并不存在,在运行期,根据条件,动态生成。
这一点和前期绑定、后期绑定类似,可以参考《java编程思想》第150、151页。

静态代理

简述下静态代理,我们用黄牛代购火车票,写一个demo,说明之:

接口类:Station

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.proxy.statices;
/**
* 接口,给出提供的方法
*
*/
public interface Station {
/**
* 买票
*/
public void saleTicket();
/**
* 退票
*/
public void returnTicket();
}

实现类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.proxy.statices;
/**
* 真正处理业务的对象,也就是真正的火车站,处理买票,退票
*/
public class RealStation implements Station{
@Override
public void saleTicket() {
System.out.println("杭州火车站:出售了一张票");
}
@Override
public void returnTicket() {
System.out.println("杭州火车站:退回了一张票");
}
}

代理对象,黄牛:

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
package com.proxy.statices;
/**
* 黄牛,专门提供 代理购买火车票的服务
*/
public class YellowCow implements Station{
//需要真正持有target对象
private Station station;
public YellowCow(Station station){
this.station = station;
}
@Override
public void saleTicket() {
System.out.println("黄牛甲:提供代理购票服务");
station.saleTicket();
System.out.println("黄牛甲:代理购票成功");
}
@Override
public void returnTicket() {
System.out.println("黄牛甲:提供代理退票服务");
station.returnTicket();
System.out.println("黄牛甲:代理退票成功");
}
}

client对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.proxy.statices;
public class PersonNeedTicket {
public static void main(String[] args) {
Station station = new YellowCow(new RealStation());
station.saleTicket();
System.out.println("===========");
station.returnTicket();
}
}

说明,由于Proxy对象看上去就是一个target对象,所以,Proxy和Target需要实现相同的接口。
类图如下:

动态代理

demo

我们先写一个demo, 然后再根据demo具体分析:

接口类:

1
2
3
4
5
6
7
8
9
10
11
package com.proxy.classic;
/**
* 接口, target需要实现该接口
*
*/
public interface Person {
public void say();
}

真实的 实现类:

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.proxy.classic;
/**
* target, 被代理的类
*/
public class Singer implements Person{
@Override
public void say() {
System.out.println("我是歌手,我会唱歌");
}
}

动态代理依赖的 InvocationHandler的实现类

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
package com.proxy.classic;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* 调用target的入口,所有的方法调用都要经过invoke方法
*
*/
public class PersonHandler implements InvocationHandler{
// target,被代理的对象
private Object obj;
public PersonHandler(Object obj){
this.obj = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before invoke method, do something");
Object result = method.invoke(obj, args);
System.out.println("after invoke method, do something");
return result;
}
}

客户端使用类:

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
package com.proxy.classic;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class Client {
public static void main(String[] args) {
// 生成被调用的对象,即:target
Person person = new Singer();
// 生成调用对象的handler,该对象需要持有真正的被代理对象target
InvocationHandler handler = new PersonHandler(person);
// 将生成的代理对象二级制流,写到文件中
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
// 动态生成代理对象, 即: proxy, 代理对象和被代理对象实现了相同的接口
Person proxy = (Person)Proxy.newProxyInstance(person.getClass().getClassLoader(), person.getClass().getInterfaces(), handler);
// 调用代理对象的方法,代理对象会通过 invocationhandler调用被代理对象的方法
proxy.say();
}
}

一个最简单的动态代理已经完成了。那么,Proxy.newProxyInstance()方法做了什么事情?为什么我们要实现InvocationHandler接口? 客户端既然已经持有了真实的对象(上例是Singler类),为什么还要绕个弯用动态代理调用呢?带着这些问题,我们逐一分析,分析之前,还是先上一张上例的类图:

简要说明

1、Proxy.newInstance(),用来动态的生成代理对象,这个代理对象名字为$Proxy{n}(比如:$Proxy0、 $Proxy1、$Proxy2…),这个类的定义为:$Proxy0 extends java.lang.reflect.Proxy implements Person{
}

也就是说,生成的代理对象$Proxy0,和上述的静态代理的YellowCow相似,只是多了 从Proxy继承这一特性。

2、InvocationHandler,调用target对象的唯一入口,也就是,代理对象调用所有的被代理对象的方法时, 都通过Invocationhandler.invoke()方法调用。有没有想起Spring中的AOP,在某处统一打log, 统一做权限…. 都是基于此。

源码分析

Proxy.newInstance()的实现细节,需要参考源码,我仅把重要的部分摘录出来,便于理解,主要涉及以下三个方法,如果觉得太冗赘,可以跳过这一步:
Proxy.newInstance()
Proxy.ProxyClassFactory.apply()
ProxyGenerator.generateProxyClass()

1、Proxy.newInstance()的重要实现:

1
2
3
4
5
6
7
8
9
10
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces, InvocationHandler h)
throws IllegalArgumentException{
// 因为代理对象的class文件并不是编译期生成的,所以,首先需要获取生成代理对象所依赖的class对象
Class<?> cl = getProxyClass0(loader, intfs);
// 通过获取的Class对象,利用反射生成真正的代理对象$Proxy{n},这里最主要的是,我们把实现InvocationHandler接口的类,当做构造器的参数,使构造出来的对象持有InvocationHandler的实现
return cons.newInstance(new Object[]{h});
}

2、getProxyClass0方法在jdk1.8中,调用的是Proxy的内部类ProxyClassFactory的apply方法,重要实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private static final class ProxyClassFactory
implements BiFunction<ClassLoader, Class<?>[], Class<?>>
{
// 定义代理类的名称前缀
private static final String proxyClassNamePrefix = "$Proxy";
// 定义代理类的名称的后缀, 从0开始递增,所以代理类的名称为: $Proxy0、 $Proxy1、 $Proxy2、
private static final AtomicLong nextUniqueNumber = new AtomicLong();
// 生成代理类的二进制的class字节流,这一步最重要
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);
try {
//这是jvm的本地方法, 用生成的二进制流构造class对象
return defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
throw new IllegalArgumentException(e.toString());
}
}

3、ProxyGenerator.generateProxyClass()调用的主要是方法generateClassFile(),generateClassFile()的重要实现:

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
private byte[] generateClassFile() {
// 将Object的hashCode,equals,toString三个方法写到二进制流中
addProxyMethod(hashCodeMethod, Object.class);
addProxyMethod(equalsMethod, Object.class);
addProxyMethod(toStringMethod, Object.class);
// 将构造器方法、字段、以及实现的接口(也就是Person)的方法,写到二进制流中
methods.add(generateConstructor());
// add static field for method's Method object
fields.add(new FieldInfo(pm.methodFieldName,
"Ljava/lang/reflect/Method;",ACC_PRIVATE | ACC_STATIC));
// generate code for proxy method and add it
methods.add(pm.generateMethod());
// 根据jvm定义的class的标准,生成二进制字节流,
ByteArrayOutputStream bout = new ByteArrayOutputStream();
DataOutputStream dout = new DataOutputStream(bout);
dout.writeInt(0xCAFEBABE); // u4 magic;
dout.writeShort(CLASSFILE_MINOR_VERSION);// u2 minor_version;
...
...
// 把接口定义的方法,写入字节流
for (MethodInfo m : methods) {
m.write(dout);
}
...
...
// 返回字节流
return bout.toByteArray();
}

4、那么,生成的$Proxy0对象,长得什么样子呢,我们将写到磁盘的二进制文件反编译出来,如下:

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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
package com.sun.proxy;
import com.proxy.classic.Person;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0 extends Proxy implements Person
{
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
// 构造器,将InvocationHandler注入到父类的属性中
public $Proxy0(InvocationHandler paramInvocationHandler) throws {
super(paramInvocationHandler);
}
// 生成的equals方法,调用equals的方法都会调用InvocationHandler的invoke方法
public final boolean equals(Object paramObject) throws {
try
{
return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
// 生成的toString方法,调用equals的方法都会调用InvocationHandler的invoke方法
public final String toString()
throws
{
try
{
return (String)this.h.invoke(this, m2, null);
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
// 生成的say方法, 当我们在Client中调用Person.say()时,就是调用该方法,也是会通过InvocationHandler的invoke方法进行调用。
public final void say()
throws
{
try
{
this.h.invoke(this, m3, null);
return;
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
// 生成的hanhCode方法,调用equals的方法都会调用InvocationHandler的invoke方法
public final int hashCode()
throws
{
try
{
return ((Integer)this.h.invoke(this, m0, null)).intValue();
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
// 每个方法都有有对应的method对象,用来充当invoke的实参
static
{
try
{
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m3 = Class.forName("com.proxy.classic.Person").getMethod("say", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
return;
}
catch (NoSuchMethodException localNoSuchMethodException)
{
throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
}
catch (ClassNotFoundException localClassNotFoundException)
{
throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
}
}
}

调用简图

通过分析生成的代理对象,便可以清楚动态代理的处理过程,一个调用简图如下:

几个问题

通过以上分析,动态代理的对象生成过程,以及调用过程就比较清楚了。我们总结几个问题:
1、Q:为什么需要实现InvocationHandler接口?
A:因为在Proxy中,持有了一个InvocationHandler类型的属性,所有调用被代理对象的方法,都由该类的invoke()方法调用。


2、Q:InvocationHandler.invoke(Object proxy, Method method, Object[] args)的三个参数,用来做什么的?尤其是第一个参数。
A:第二个参数,method方法为生成的代理对象的方法,当执行method.invoke(target, args)的时候, 就是在target对象上调用method方法,而target由于和代理类实现了相同的方法,所以,target对象上对应的方法便会被执行。
第三个参数,用来充当方法的参数,比较好理解。
第一个参数,这个参数有点难以理解,其实这个参数就是Proxy对象,正常的情况下也许没有用,但是当需要通过反射打印代理类的信息时,或者需要回调代理类的某个方法时, 这个参数便起作用了。


3、Q:生成的代理对象,为什么要继承父类Proxy?
A:因为代理类不管执行任何代理方法,都是通过调用InvocationHandler的invoke方法完成的, 而InvocationHandler是Proxy的一个属性,这个属性放到Proxy,也就做了一个不能改的约定,即必须要对InvocationHandler进行实现。


4、Q:既然用动态代理, 那么客户端可以只看到接口,而看不见被代理类的实现吗?
A:可以,RPC就是这样实现的,服务提供者只把接口类提供给客户端,客户端便可以完成真实的调用。

总结

动态代理的虽然比较好理解,但是根据源码跟踪其实现时,还是用了很多很底层,很基础的一些知识,更为重要的是,他是AOP、RPC的基础。在现在服务化、微服务大行其道的局面下,PRC必不可少,而动态代理又是其种很重要的一环,理解其来龙去脉非常有必要。