动态代理底层原理
# 简介
java里的class文件加载分为两种情况,一种就是类型是编译器已知的,这种文件的.class文件在编译的时候,编译器会把.class文件打开检查,但是注意不是加载哦,第二种就是我们可能是从别的地方获取到了一个引用,然后动态的把这个未知类型的引用的对象的.class文件加载进jvm虚拟机里。
那么我们称前者为RTTI,即Run- Time Type Identification 运行时类型识别,有的人把RTTI翻译成 Run - Time Type Information ,我个人认为是不对的,因为我觉得它概括的不够全面,所以我建议大家把I 翻译成Identification更容易理解。称后者为“反射”。
再简单说如果该类在编译前就已知,也就是该类在classPath路径下,这就是RTTI。如果该类编译器未知,也就是在程序运行时才知道的,这就是反射
# 原理:
被代理对象:
public interface EchoService {
String echo(String str);
}
2
3
被代理对象实例:
public class DefaultEchoService implements EchoService{
@Override
public String echo(String message) {
return message;
}
2
3
4
5
InvocationHandler 实现:
public class CostInvocationHandler implements InvocationHandler {
private Object target;
public CostInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long startTime = System.currentTimeMillis();
Object result = method.invoke(target, args);
long cost = System.currentTimeMillis() - startTime;
System.out.println("cost " + cost);
return result;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
使用:
public class test {
public static void main(String[] args) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Object proxy = Proxy.newProxyInstance(classLoader,
new Class[]{EchoService.class},
new CostInvocationHandler(new DefaultEchoService()));
EchoService echoService = (EchoService) proxy;
// cost 0
// hello world
System.out.println(echoService.echo("hello world"));
}
}
2
3
4
5
6
7
8
9
10
11
12
# InvocationHandler
这个类必须有一个实现类,如上面的CostInvocationHandler
。这个类里面只包含有一个 invoke 方法。
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}
2
3
文档注释中,每一个代理对象必须有一个与之关联的invocation handler
,当在代理实例上调用方法时,方法调用将被编码并发送到其调用实例的invoke
方法。
拿上面例子解释:在执行 echo 方法的时候,就会调用 CostInvocationHandler
中的invke
方法,从而达到代理的目的。
# Proxy.newProxyInstance
这个方法中,最重要的就是生成代理对象。因为是动态生成的,所以需要利用HSDB (opens new window)去探究生成的字节码文件。下面就反编译后的文件:
public final class $Proxy0 extends Proxy implements EchoService {
private static Method m1;
private static Method m3;
private static Method m2;
private static Method m0;
public $Proxy0(InvocationHandler var1) {
super(var1);
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m3 = Class.forName("com.aop.EchoService").getMethod("echo", Class.forName("java.lang.String"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
public final String echo(String var1) {
try {
return (String)super.h.invoke(this, m3, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
}
//注意,这里为了节省篇幅,省去了toString,hashCode、equals方法的内容。原理和giveMoney方法一毛一样。
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
1、在 Proxy.newInstance 动态生成了 $Proxy0,并且这时候传给了构造函数 CostInvocationHandler
;
2、$Proxy0
的构造函数中,调用了 super()
;这时候把 CostInvocationHandler
传给父类,并且赋值给 h
;
public class Proxy{
protected Proxy(InvocationHandler h) {
Objects.requireNonNull(h);
this.h = h;
}
}
2
3
4
5
6
3、调用 echo方法的时候,就会调用super.h.invoke()
,即Proxy 类的h,也就是调用CostInvocationHandler
的invke
方法。
这里invoke 方法又是 Method 的invke。反射的底层原理 (opens new window)
# 总结
jdk为我们的生成了一个叫$Proxy0(这个名字后面的0是编号,有多个代理类会一次递增)的代理类,这个类文件时放在内存中的,我们在创建代理对象时,就是通过反射获得这个类的构造方法,然后创建的代理实例。
我们可以对InvocationHandler看做一个中介类,中介类持有一个被代理对象,在invoke方法中调用了被代理对象的相应方法。