南贺神社

你写程序有写诗一样的感觉吗?

0%

jvm原理(36)透过字节码生成审视Java动态代理运作机制

我们在使用spring这类框架的时候,基于动态代理的使用,比如AOP,会使得开发更加灵活,那么在字节码的层面动态代理是什么样子的呢,生成出来的代理类结构是什么,本次我们首先写一个动态代理的例子,然后得到生成的动态代理类。
定义接口:

1
2
3
public interface SubJect {
void request();
}

定义实现类:

1
2
3
4
5
6
public class RealSubJect implements SubJect {
@Override
public void request() {
System.out.println("method calling");
}
}

定义动态代理类:

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

private Object sub;

public DynamicSubject(Object obj){
this.sub = obj;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("befor method call");
method.invoke(sub,args);
System.out.println("after method call");
return null;
}
}

定义客户端:

1
2
3
4
5
6
7
8
9
10
public class Client {
public static void main(String[] args) {
RealSubJect realSubJect = new RealSubJect();
InvocationHandler invocationHandler = new DynamicSubject(realSubJect);
SubJect subJect = (SubJect) Proxy.newProxyInstance(realSubJect.getClass().getClassLoader(),realSubJect.getClass().getInterfaces(),invocationHandler);
subJect.request();
System.out.println(subJect.getClass());//打印动态代理类的class
System.out.println(subJect.getClass().getSuperclass()); //打印父类
}
}

运行客户端得到结果:

1
2
3
4
5
befor method call
method calling
after method call
class com.sun.proxy.$Proxy0
class java.lang.reflect.Proxy

那么这个com.sun.proxy.$Proxy0是怎么出来的呢,这个需要进入到Proxy.newProxyInstance()里边看一下他的逻辑:

1
2
3
4
5
6
7
8
Proxy.newProxyInstance()
-->getProxyClass0()
-->proxyClassCache.get()[通过ProxyClassFactory获取]
-->WeakCache.Factory.get()
-->valueFactory.apply(key, parameter)
-->Proxy.apply()
-->byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);
-->byte[] proxyClassFile = ProxyGenerator.generateClassFile();

ProxyGenerator是 sun.misc包里边的,我们得到的代码是ide反编译的结果,我们贴出来,var4是生成出来的字节数组,然后
saveGeneratedFiles是一个开关,如果为true就会把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
27
28
public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2) {
ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);
final byte[] var4 = var3.generateClassFile();//返回最终的字节数组
if (saveGeneratedFiles) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
try {
int var1 = var0.lastIndexOf(46);
Path var2;
if (var1 > 0) {
Path var3 = Paths.get(var0.substring(0, var1).replace('.', File.separatorChar));
Files.createDirectories(var3);
var2 = var3.resolve(var0.substring(var1 + 1, var0.length()) + ".class");
} else {
var2 = Paths.get(var0 + ".class");
}

Files.write(var2, var4, new OpenOption[0]);
return null;
} catch (IOException var4x) {
throw new InternalError("I/O exception saving generated file: " + var4x);
}
}
});
}

return var4;
}

saveGeneratedFiles的定义:

1
2
private static final boolean saveGeneratedFiles =
((Boolean)AccessController.doPrivileged(new GetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles"))).booleanValue();

ok 我们设置一下sun.misc.ProxyGenerator.saveGeneratedFiles就可以得到class文件。下边这行代码放在Client main方法的第一行,提前设置。

1
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");

运行Client,然后在我们的工程里边就会出现一个目录
com.sun.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
package com.sun.proxy;

import com.twodragonlake.jvm.bytecode.SubJect;
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 SubJect {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;

/**
构造方法
这个构造方法在Proxy类的newProxyInstance方法里边,有一个地方是获取代理类的构造器:
Class<?> cl = getProxyClass0(loader, intfs); //代理类
final Constructor<?> cons = cl.getConstructor(constructorParams);//获取代理类的构造器
return cons.newInstance(new Object[]{h});//实例化代理类;
h是InvocationHandler,InvocationHandler是我们定义的DynamicSubject的接口,这个时候就会调用当前的这个构造方法
动态代理类的父类Proxy持有InvocationHandler的引用,完成这个引用的赋值。
*/
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}

public final boolean equals(Object var1) throws {
try {
/**
调用Proxy的InvocationHandler的invoke方法,其实就是调用DynamicSubject的invoke方法,
因为DynamicSubject实现了InvocationHandler,m1是Object类的equals方法,new Object[]{var1}是equals的方法参数
如果DynamicSubject重新了equals方法就会转发到DynamicSubject的equals方法,否则就是调用Object的equals方法
*/
return ((Boolean)super.h.invoke(this, m1, new Object[]{var1})).booleanValue();
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}

/**
和equals方法道理了一样
*/
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 request() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}

/**
和equals方法道理了一样
*/
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 {
//类加载的时候,将Object类的是个方法拿出来
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.twodragonlake.jvm.bytecode.SubJect").getMethod("request");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}

除了equals、toString、hashCode的其他的Object的方法,都不会得到代理类的转发,原来是什么样子的,代理后也还是什么样子的,不会发生变化。
好,到目前为止我们分析了动态代理类的源码,那回到client的代码:

1
SubJect subJect = (SubJect) Proxy.newProxyInstance(realSubJect.getClass().getClassLoader(),realSubJect.getClass().getInterfaces(),invocationHandler);

subJect返回的是一个动态代理类,这个类的名字是$Proxy0,$Proxy0的父类是Proxy,父类持有InvocationHandler的引用,也就是持有DynamicSubject的引用,同时$Proxy0实现了SubJect接口,$Proxy0里边生成了DynamicSubject里边所声明的方法,并且转发到了DynamicSubject里边。当我们调用subJect.request();
的时候就是调用了$Proxy0.request();$Proxy0将方法的调用通过父类Proxy持有的InvocationHandler的引用,即【super.h.invoke(this, m0, (Object[])null))】进行了转发,转发到DynamicSubject的invoke方法。所以说DynamicSubject的invoke方法的第一个参数是proxy,接下里就会打印:

1
2
3
befor method call
method calling
after method call

动态代理的优势:
代理对象可以在没有真实对象不存在的情况下提前生成代理对象,代理对象可以代理多种真实对象,而且jdk的动态代理是面向接口的。
cglib:
面向继承的代理,子类可以重写父类的实现,同时代理类可以调用父类的方法(jdk动态代理没有这个优势,因为jdk动态代理是面向接口的)