[toc]
jdk动态代理#
实现jdk动态代理时需要哪些步骤?#
- 有一个目标类Class, 目标类必须implment某个行为接口Inteface
- 有一个代理类XxxProxy,实现自InvocationHandler接口
- 代理类必须实现一个invoke方法, invoke方法的入参是 被代理对象(目标对象)、执行方法(method的反射对象)、执行参数
通过调用“ 执行方法.invoke(被代理对象,执行参数)” 这个常见的反射操作, 即可执行实际调用方法,然后你可以在这个方法的前后做各种处理或者改造。 - 使用Proxy.newProxyInstance(被代理类的类加载器, 被代理类的接口, 这个proxy代理对象)生成一个做过绑定的代理对象,能被调用的方法都是行为接口Inteface里的。
JDK动态代理的invoke原理是什么?#
proxy类里面真正的那个代理方法字节码类似如下(这个类就是给被别人调用的代理类,是newProxyxxx()操作后的那个类,不是invokeHandler类):
1 | Proxy0.class |
m3就是通过static初始化阶段生成一个m方法, 然后调用sayHello时,调用invokeHandler.invoke,从而走进他里面实现的那个方法。
Proxy.newProxyInstance()会通过调用sun.misc.ProxyGenerator.generateProxyClass()来生成一个字节码,从而得到一个描述代理类的字节码数组。
生成字节码的过程就是根据class文件的格式规范去拼装字节码。
更多源码逻辑见Proxy源码解析
为什么Proxy.newProxyInstance必须要传入一个类加载器?#
因为他需要创建一个新的proxy类时,必须要基于接口去构造一个新的类对象,后面再使用类对象去反射一个实际代理对象
为什么jdk动态代理必须依赖一个接口?底层逻辑是什么?#
因为真正给用户调用的那个代理对象类XXXProxy$0, 实际上他为了做相关的代理操作(比如将inovkeHnandler作为成员,并调用各种字节码生产方法),需要extends Proxy这个类
也就是说他的类结构长这样:
因为java是单继承,导致父类已经被Proxy占用了,但你有需要对Worker对象做代理封装,并提供这个接口对外提供的方法,因此只能用implements的形式。
另一个更好的解释:
Cglib代理实际上是通过继承,也就是生成一个继承被代理对象的类,编译成class文件时还会额外生成一个fastclass文件
该文件记录各个method的class索引(类名+方法名+参数),当执行某个方法时,通过计算索引,定位到具体的方法,代理对象执行该方法,然后super调用父类(执行了被代理对象的方法)。
生成代理对象时通过fastclass索引机制直接定位到被代理对象的class文件,从而实现反复调用,等于说是class复用,每次都是直接拿被代理对象的class内容执行的。
那么spring里AOP所用的CGLIB为啥不需要接口呢?#
因为CGLIB是直接继承被代理类做字节码增加的,相当于做了字节码改造。
而jdk动态代理需要继承自Proxy类,利用父类的机制引用invokeHandler+反射的方法,来做代理操作。
那么CGLIB和jdk动态代理哪个更好?#
- 性能上比较
- jdk动态代理生成类速度快,调用慢
- cglib生成类速度慢,但后续调用快
但是实际上JDK的速度在版本升级的时候每次都提高很多性能,而CGLIB仍止步不前.
在对JDK动态代理与CGlib动态代理的代码实验中看,1W次执行下,JDK7及8的动态代理性能比CGlib要好20%左右。
- 稳定性上比较
- jdk动态代理Java本身支持,不用担心依赖问题,随着版本稳定升级和优化。
- 而CGLIB是外部技术,字节码库需要进行更新以保证在新版java上能运行
;
- 使用上比较
jdk动态代理必须依赖接口,CGLIB不需要,在设计不当的历史包袱下
如果必须对非接口对象做代理,那么只能用CGLIB临时过度。
因此spring实现AOP时,都是优先使用jdk动态代理,如果没有实现接口,才改成CGLIB过度,这也是为什么我们spring里的service类一般都要先定义1个接口,即使你只有1个service实现类。