本文承接自动态代理浅析这篇文章,对代理没有什么概念的同学建议先读下这篇文章。

1. 使用JDK动态代理

JDK动态代理使用起来很简单,如下:

TestService是一个业务接口,接口中有个test方法, TestServiceImpl是TestService的实现类。
InvocationHandler是JDK动态代理的调用代理方法处理接口,我们JDK使用动态代理时需要实现这个接口,在这个接口的处理方法中编写处理逻辑,你想怎样控制目标方法的访问都可以在这个方法中实现。然后调用Proxy类的静态方法newProxyInstance方法即可获得到代理类实例。

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
public class Main {
public static void main(String args[]) throws ClassNotFoundException, InvocationTargetException, IllegalAccessException, NoSuchMethodException {

TestServiceImpl testService = new TestServiceImpl();
// 声明自己的处理类
MyInvokeHandler myInvokeHandler = new MyInvokeHandler(testService);
// 第一次生成代理类
TestService proxy = (TestService) Proxy.newProxyInstance(TestService.class.getClassLoader(),new Class[]{TestService.class},myInvokeHandler);
// 代理类调用接口方法
proxy.test();
}

class TestServiceImpl implements TestService{
@Override
public void test(){
System.out.println("我要开始测试啦");
}
}

class MyInvokeHandler implements InvocationHandler{
Object obj;
public MyInvokeHandler(Object obj){
this.obj = obj;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//在反射调用之前,可以加一些处理行为
// doSomeThing();
method.invoke(obj,args);
//在反射调用之后,也可以加一些处理行为
// doSomeThing();
return null;
}
}

interface TestService{
void test();
}

Java通过Proxy类和InvocationHandler接口生成动态代理类$Proxy0Proxy类是JDK生成动态代理的核心类,包含了JDK动态代理生成代理类的大部分逻辑。

2. Java动态代理源码解析

Class对象每个类只有一个(同一个类加载器的情况下),该Class对象在类加载阶段生成,存储在内存中,非Java虚拟机堆,是该类对外访问的唯一入口。Java Language Specification 12.4

Java生成动态代理类的核心方法是:ProxyClassFactory的Class<?> apply(ClassLoader loader, Class<?>[] interfaces)

参数:ClassLoader loader接口类加载器,Class<?>[] interfaces接口类的Class。

下面代码主要作用就是通过遍历接口数组,校验接口数组中的数据是否合法。

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
// 遍历接口类的Class数组
for (Class<?> intf : interfaces) {

Class<?> interfaceClass = null;
try {
// 反射获得接口类Class对象
interfaceClass = Class.forName(intf.getName(), false, loader);
} catch (ClassNotFoundException e) {

}
// 校验是否是同一个类加载器,如果是不同的类加载器,生成的接口Class对象是不同的
if (interfaceClass != intf) {
throw new IllegalArgumentException(
intf + " is not visible from class loader");
}
// Java动态代理仅支持接口代理
if (!interfaceClass.isInterface()) {
throw new IllegalArgumentException(
interfaceClass.getName() + " is not an interface");
}
// 使用Set来验证传入的接口数组中是否存在相同的接口
if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
throw new IllegalArgumentException(
"repeated interface: " + interfaceClass.getName());
}
}

下面这么多代码是为了给下面生成的代理类找到一个合理的包名和类名,如果接口数组中有非public属性的接口,如果此接口的包名不为null,则使用这个接口的包名,否则使用默认的com.sun.proxy包名,类名则是“上述包名+$Proxy+自增数字”。

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
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;
} else if (!pkg.equals(proxyPkg)) {
throw new IllegalArgumentException(
"non-public interfaces from different packages");
}
}
}

if (proxyPkg == null) {
// if no non-public proxy interfaces, use com.sun.proxy package
proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
}

// 使用自增数字区分不同的类
long num = nextUniqueNumber.getAndIncrement();
String proxyName = proxyPkg + proxyClassNamePrefix + num;

下面就是整个动态代理最核心的代码。生成代理类class文件的字节码,根据这个文件的字节码生成代理类的Class对象。

1
2
3
4
5
6
7
8
9
10
// 生成字节数组,这个字节数组是生成的Class文件的字节数组,将这个字节流输出到class文件,就是上面的$Proxy0代理类。  
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
try {
// 通过刚才生成的代理类文件,生成代理类的Class对象。
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
throw new IllegalArgumentException(e.toString());
}

上面的代码仅仅是生成动态代理类的逻辑,不是JDK Proxy生成动态代理类实例的流程。

建议下面跟着源码一点点看如下的流程图。

weakcache

第一个结构图是WeakCache的缓存结构图,WeakCache是Proxy的一个实例,每次去创建代理时,都会先去访问WeakCache,WeakCache中没有才会去创建。WeakCache采用两级缓存机制,第一层使用classloader生成的弱引用key,map实例valueMap的value在ConcurrentHashMap map中,valueMap是ConcurrentHasshMap, valueMap中使用接口数组(创建代理类传入的接口数组参数)生成的subKey,这个subKey不是弱引用,valueMap的value即可能是WeakCache.Factory实例,也可能是实现Supplier接口和继承WeakReference类的CacheValue实例。CacheValue用于封装动态生成的代理类的Class对象,WeakCache.Factory中包含生成动态代理类字节码和Class对象的逻辑,这个逻辑就是上面源码分析中的apply方法。
第二个是流程图,使用Proxy.newProxyInnstance生成动态代理类时,读取缓存的流程,结合着结构图和代码,应该可以理解JDK Proxy的整个缓存存储结构及读取流程。

生成的$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.zhongyp.advanced.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

// 这个地方有两个接口,是因为我之前做测试的时候多加了一个TestService1接口。
final class $Proxy0 extends Proxy implements TestService, TestService1 {

// TestSerivce,TestService1两个接口总共就4个方法,test(),test1(),test3(),test4(),这里有7个方法,其中三个是hashCode,toString,equals。
private static Method m1;
private static Method m3;
private static Method m4;
private static Method m6;
private static Method m2;
private static Method m5;
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});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}

// 接口中的所有方法都会在代理类中生成,然后将代理类中的方法对象与方法名匹配放到InvocaotionHandler实现类实例的方法参数中
public final void test() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}

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

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

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 test1() throws {
try {
super.h.invoke(this, m5, (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);
} 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"));
m3 = Class.forName("com.zhongyp.advanced.proxy.TestService").getMethod("test");
m4 = Class.forName("com.zhongyp.advanced.proxy.TestService").getMethod("test3");
m6 = Class.forName("com.zhongyp.advanced.proxy.TestService1").getMethod("test4");
m2 = Class.forName("java.lang.Object").getMethod("toString");
m5 = Class.forName("com.zhongyp.advanced.proxy.TestService1").getMethod("test1");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}

关于JDK动态代理的调用很简单,上面是我们上面的例子输出的动态代理类反编译的代码,我们看到接口中的每个方法super.h.invoke(this, m5, (Object[])null),super就是Proxy,h是Proxy中的InvocationnHandler实例,InvocationHandler实例反射调用你的方法。InvocationHandler是在生成动态代理类的class对象后,创建动态代理类实例作为构造参数传进去的,所以虽然当前接口参数相同时,可能返回同一个动态代理类Class对象,但是只要InvocationHandler不同,他们就是不同的实例对象。

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

jdk-proxy-invoke

优点:

  • 最小化依赖关系,减少依赖意味着简化开发和维护,JDK本身的支持,可能比cglib更加可靠。
  • 平滑进行JDK版本升级,而字节码类库通常需要进行更新来保证在新版Java上能够使用。
  • 代码实现简单。

FAQ

  1. 为什么JDK动态代理仅支持实现接口类的动态代理?

这个问题我觉得可以从JDK做动态代理的初衷来说,JDK动态代理类是在运行时实现指定的接口列表的类,这个是JDK在设计实现动态代理最初就已经确定了的,所以可以看到在生成代理类的过程中,基本上就把接口类当作一个已知条件在使用,包括在定义缓存使用的subKey,代理类包名的生成规则中,反射获取Method对象等等地方,所以不存在为什么只支持实现接口类,而是JDK动态代理类的设计就是这样子的。

  1. 为什么WeakCache采用两级缓存接口?

这个问题我们可以从两个缓存的不同点来看,第一层缓存map是一个弱引用key,非弱引用valueMap,第二层缓存valueMap使用的是非弱引用subKey,弱引用CacheValue。我们都知道弱引用只要有垃圾回收时就会被回收,主要是为了防止缓存太多导致服务频繁的FullGC,所以第一层的作用就是当垃圾回收时,将缓存的valueMap全部清空。还有一个原因是第一层缓存使用的是classloader生成的key,所以map其实缓存的是所有当前同一个classloader生成的代理类的class对象。再说第二层缓存,第二层缓存使用的是创建代理类时传入的接口数组生成的subKey,这个是为了区分实现不同接口的动态代理类Class对象,如果说两个类都实现了同一个接口,那岂不是获取的同样的Class对象,这么说也是对的,区别在于实现的InvocationHandler接口的子类h不同,而这个h才是生成代理类实例的最终区别。第二层缓存为什么CacheValue使用弱引用,原因在于第一层缓存虽然key时弱引用,但是value不是,所以垃圾回收时只会回收key,不会回收value,value只会在下一次调用Proxy.newProxyInstance方法时才会去清空无效key的value。所以为了value能及时清空,所以CacheValue也使用了弱引用。