0%

jdk动态代理原理解析

[toc]

jdk动态代理#

实现jdk动态代理时需要哪些步骤?#

  1. 有一个目标类Class, 目标类必须implment某个行为接口Inteface
  2. 有一个代理类XxxProxy,实现自InvocationHandler接口
  3. 代理类必须实现一个invoke方法, invoke方法的入参是 被代理对象(目标对象)、执行方法(method的反射对象)、执行参数
    通过调用“ 执行方法.invoke(被代理对象,执行参数)” 这个常见的反射操作, 即可执行实际调用方法,然后你可以在这个方法的前后做各种处理或者改造。
  4. 使用Proxy.newProxyInstance(被代理类的类加载器, 被代理类的接口, 这个proxy代理对象)生成一个做过绑定的代理对象,能被调用的方法都是行为接口Inteface里的。

JDK动态代理的invoke原理是什么?#

proxy类里面真正的那个代理方法字节码类似如下(这个类就是给被别人调用的代理类,是newProxyxxx()操作后的那个类,不是invokeHandler类):

1
2
3
4
5
6
7
8
9
10
11
Proxy0.class
public final void sayHello(object[] args) {
try {
// this.h就是继承了invokeHandler的那个类对象
this.h.invoke(this, m3, args);
} catch(Excetion ...) {
}
}
static {
m3 = Class.forname("那个接口的类名").getMethod("sayHello");
}

m3就是通过static初始化阶段生成一个m方法, 然后调用sayHello时,调用invokeHandler.invoke,从而走进他里面实现的那个方法。

Proxy.newProxyInstance()会通过调用sun.misc.ProxyGenerator.generateProxyClass()来生成一个字节码,从而得到一个描述代理类的字节码数组。
生成字节码的过程就是根据class文件的格式规范去拼装字节码。

更多源码逻辑见Proxy源码解析


为什么Proxy.newProxyInstance必须要传入一个类加载器?#

48a3a4503f728f9ce9520a2c4820e9a9858c237a
因为他需要创建一个新的proxy类时,必须要基于接口去构造一个新的类对象,后面再使用类对象去反射一个实际代理对象
08a7c84b1abfab8a47fa544fd0de4603c1699e88


为什么jdk动态代理必须依赖一个接口?底层逻辑是什么?#

因为真正给用户调用的那个代理对象类XXXProxy$0, 实际上他为了做相关的代理操作(比如将inovkeHnandler作为成员,并调用各种字节码生产方法),需要extends Proxy这个类
也就是说他的类结构长这样:
5d5edf4370eff2c399096e1fefdd76e9cb7c90ee
因为java是单继承,导致父类已经被Proxy占用了,但你有需要对Worker对象做代理封装,并提供这个接口对外提供的方法,因此只能用implements的形式。

另一个更好的解释:

Cglib代理实际上是通过继承,也就是生成一个继承被代理对象的类,编译成class文件时还会额外生成一个fastclass文件
该文件记录各个method的class索引(类名+方法名+参数),当执行某个方法时,通过计算索引,定位到具体的方法,代理对象执行该方法,然后super调用父类(执行了被代理对象的方法)。
生成代理对象时通过fastclass索引机制直接定位到被代理对象的class文件,从而实现反复调用,等于说是class复用,每次都是直接拿被代理对象的class内容执行的。


那么spring里AOP所用的CGLIB为啥不需要接口呢?#

因为CGLIB是直接继承被代理类做字节码增加的,相当于做了字节码改造。

而jdk动态代理需要继承自Proxy类,利用父类的机制引用invokeHandler+反射的方法,来做代理操作。


那么CGLIB和jdk动态代理哪个更好?#

  1. 性能上比较
  • jdk动态代理生成类速度快,调用慢
  • cglib生成类速度慢,但后续调用快

但是实际上JDK的速度在版本升级的时候每次都提高很多性能,而CGLIB仍止步不前.
在对JDK动态代理与CGlib动态代理的代码实验中看,1W次执行下,JDK7及8的动态代理性能比CGlib要好20%左右。

JDK 动态代理和Cglib性能对比

  1. 稳定性上比较
  • jdk动态代理Java本身支持,不用担心依赖问题,随着版本稳定升级和优化。
  • 而CGLIB是外部技术,字节码库需要进行更新以保证在新版java上能运行
  1. 使用上比较
    jdk动态代理必须依赖接口,CGLIB不需要,在设计不当的历史包袱下
    如果必须对非接口对象做代理,那么只能用CGLIB临时过度。

因此spring实现AOP时,都是优先使用jdk动态代理,如果没有实现接口,才改成CGLIB过度,这也是为什么我们spring里的service类一般都要先定义1个接口,即使你只有1个service实现类。