JDK动态代理的实现

什么是动态代理

问题引入

有这样一个类:

1
2
3
4
5
6
7
public class Hello {

public void say() {
System.out.println("hello world");
}

}

现在有一个需求:需要在Hello类运行say()方法前后使用日志记录下运行的时间戳,该怎么做?

最直观的方式就是在say()方法的前后加上日志记录当前的时间戳,像这样:

1
2
3
4
5
6
7
public void say() {
System.out.println(System.currentTimeMillis());//用控制台输出代替日志记录

System.out.println("hello world");

System.out.println(System.currentTimeMillis());//用控制台输出代替日志记录
}

但是这个时候,我们又有了一个新的类叫做GoodBye类,它也有个say()方法,我么也需要为它的say()方法添加日志记录,这时候怎么办?

又手动为其方法前后添加日志记录逻辑?要是有一千个say()方法需要添加日志记录呢?

这个时候我们可以使用装饰者模式来给业务代码添加“修饰”。

首先,我们需要为say()方法抽象出一个接口,我们暂时把这个接口叫做Hello吧:

1
2
3
public interface Hello {
void say();
}

然后创建我们的装饰器,为HelloImpl的say方法添加“修饰”代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class HelloDecorator implements Hello {

private Hello hello;

public HelloDecorator(Hello hello) {
this.hello = hello;
}

public void say() {
System.out.println(System.currentTimeMillis());
this.hello.say();
System.out.println(System.currentTimeMillis());
}
}

这样,我们在创建HelloDecorator装饰器的时候,只需将实现了Hello接口的实例对象作为参数传给装饰器,调用装饰器的say()方法就可以实现为say()方法添加日志记录的逻辑了。

这个时候我们虽然是实现了对于所有实现了Hello接口的类的say()方法的日志记录,但是我们如果还需要为goodBye()方法记录日志呢?若是有一千个方法需要记录日志呢?

这个时候就需要动态代理了,其实动态代理的本质就是:动态的生成一个类实现某个接口,为接口的每一个方法添加一个固定的逻辑。

JDK的动态代理

在分析JDK动态代理之前,我们先来看下JDK的动态代理是怎么使用的。

还是以上面的例子为例:我们需要在一个say()方法运行前后为其添加日志记录。

首先,我们需要一个实现了InvocationHandler接口的handler,并实现了接口的invoke方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class HelloInvocationHandler implements InvocationHandler {

private Hello hello;

public HelloInvocationHandler(Hello hello) {
this.hello = hello;
}

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(System.currentTimeMillis());
Object o = method.invoke(hello, args);
System.out.println(System.currentTimeMillis());
return o;
}
}

然后写我们的main方法:

1
2
3
4
5
6
public static void main(String[] args) {
Hello hello = new HelloImpl();
HelloInvocationHandler h = new HelloInvocationHandler(hello);
Hello proxyHello = (Hello) Proxy.newProxyInstance(hello.getClass().getClassLoader(), hello.getClass().getInterfaces(), h);
proxyHello.say();
}

这样我们就实现了为say()方法添加日志记录的逻辑,但不仅限于say()方法哦,hello类中的所有方法都已经添加了日志记录的逻辑,只需要你通过proxyHello去调用其方法就行了。

JDK动态代理的实现

其实JDK的动态代理的实现是很简单的,就是动态的为我们的类去生成一个装饰器类,我们可以来看看JDK为我们生成的这个装饰器类是什么样的。

我们只需要在newProxyInstance之前设置一下系统参数System.getProperties().put(“sun.misc.ProxyGenerator.saveGeneratedFiles”, “true”),运行后就会在我们的工程目录下有一个JDK为我们生成的一个装饰器类。

在这个例子中,反编译下JDK为我们生成的装饰器类的源码如下:

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
package com.sun.proxy;

import com.dj.proxy.Hello;
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 Hello {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;

public $Proxy0(InvocationHandler var1) throws {
super(var1);
}

public final boolean equals(Object var1) throws {
try {
return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}

public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}

public final void say() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}

public final int hashCode() throws {
try {
return ((Integer)super.h.invoke(this, m0, (Object[])null)).intValue();
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}

static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("com.dj.proxy.Hello").getMethod("say");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}

可以看到,这个类的类名叫“$Proxy0”,在com.sun.proxy这个包下面,并且这个也实现了Hello接口,而其say()方法里面的代码是调用了其父类的h属性的invoke方法。而这个类的构造方法也是通过父类的构造方法实现的,接着,我们来看下它的父类的h属性和构造方法。

$Proxy0父类构造方法及属性:

1
2
3
4
5
6
protected InvocationHandler h;

protected Proxy(InvocationHandler h) {
Objects.requireNonNull(h);
this.h = h;
}

属性h就是一个InvocationHandler,而在JDK为我们生成的装饰器的方法中就是调用InvocationHandler的invoke方法为我们的类“装饰的”。

而这个InvocationHandler类是怎么来的呢?还记得我们在调用Proxy的newProxyInstance方法的时候传的一个我们自己实现了InvocationHandler接口的类吗?这里的InvocationHandler类就是我们自己实现了InvocationHandler接口的那个类,而JDK为我们生成的的装饰器类的装饰代码就是我们自己在invoke方法中写的,所以绕来绕去又绕回来了。

JDK动态代理源码分析

注:本节中所有的JDK源代码都是去掉安全检测异常精简过后的,这样看上去结构更加清晰

最后我们来看下JDK动态代理的源码,看看它到底是怎么实现的。

回到我们使用JDK动态代理的main函数中,在main函数中,我们通过Proxy.newProxyInstance()为HelloImpl创建了一个代理类,这里就是JDK代理的入口,我们跟进去看看:

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
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
//首先检查InvocationHandler,不能为空
Objects.requireNonNull(h);

final Class<?>[] intfs = interfaces.clone();

//生成装饰器类的字节码,JDK动态代理核心方法
Class<?> cl = getProxyClass0(loader, intfs);

//获取到装饰器类的有InvocationHandler参数的构造方法
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
//返回构造器实例对象
return cons.newInstance(new Object[]{h});

getProxyClass0方法源码:

1
2
3
4
5
6
7
8
9
10
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
//接口个数不能超过65535个......
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}

//从缓存中取
return proxyClassCache.get(loader, interfaces);
}

可以看到,JDK在获取装饰器类的时候还做了一层缓存,只有在缓存中没找到的时候才会去生成装饰器类,生成装饰器类如下所示:

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
private static final class ProxyClassFactory
implements BiFunction<ClassLoader, Class<?>[], Class<?>>
{

//所有装饰器类的名字都是以这个作为前缀的
private static final String proxyClassNamePrefix = "$Proxy";
//所有装饰器类名的后缀
private static final AtomicLong nextUniqueNumber = new AtomicLong();

@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {

//这里的代码是把所有的接口都load进内存,防止有些接口在内存中还没有
for (Class<?> intf : interfaces) {
Class<?> interfaceClass = null;
interfaceClass = Class.forName(intf.getName(), false, loader);
}

String proxyPkg = null;
int accessFlags = Modifier.PUBLIC | Modifier.FINAL;

//下面都是生成包名的逻辑
for (Class<?> intf : interfaces) {
int flags = intf.getModifiers();
if (!Modifier.isPublic(flags)) {
accessFlags = Modifier.FINAL;
String name = intf.getName();
int n = name.lastIndexOf('.');
String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
if (proxyPkg == null) {
proxyPkg = pkg;
}
}
}

//当通过上面的逻辑生成的包名为空的时候采用默认的包名,即:com.sun.proxy
if (proxyPkg == null) {
proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
}

//生成完整的类名
long num = nextUniqueNumber.getAndIncrement();
String proxyName = proxyPkg + proxyClassNamePrefix + num;

//通过sun的非公开方法生成类的字节码
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);

//通过本地方法生成类的实例对象并返回
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);

}
}

总结

通过剖析JDK动态代理的实现,最后我们可以这么说:JDK的动态代理其实就是JDK为我们动态的生成了一个装饰器类。

最后为读者留一个问题,也是面试中经常会被问到的问题:为什么JDK的动态代理只支持接口的代理而不支持类继承的代理?

本文首发于我在万达摆地摊's blog,转载请注明来源