上一篇我们说过了 jdk 动态代理, 这一篇我们来看看 CgLib 动态代理, 本来以为 CGLib 动态代理和 JDK 实现的方式差不多的, 但是仔细了解一下之后还是有很大的差异的, 这里我们先简单说一下这两种代理方式最大的区别, JDK 动态代理是基于接口的方式, 换句话来说就是代理类和目标类都实现同一个接口, 那么代理类和目标类的方法名就一样了, 这种方式上一篇说过了; CGLib 动态代理是代理类去继承目标类, 然后重写其中目标类的方法啊, 这样也可以保证代理类拥有目标类的同名方法;
看一下 CGLib 的基本结构, 下图所示, 代理类去继承目标类, 每次调用代理类的方法都会被方法拦截器拦截, 在拦截器中才是调用目标类的该方法的逻辑, 结构还是一目了然的;
1.CGLib 的基本使用
使用一下 CGLib, 在 JDK 动态代理中提供一个 Proxy 类来创建代理类, 而在 CGLib 动态代理中也提供了一个类似的类 Enhancer;
使用的 CGLib 版本是 2.2.2, 我是随便找的, 不同的版本有点小差异, 建议用 3.x 版本的..... 我用的 maven 项目进行测试的, 首先要导入 cglib 的依赖
- <dependency>
- <groupId>cglib</groupId>
- <artifactId>cglib</artifactId>
- <version>2.2.2</version>
- </dependency>
目标类 (一个公开方法, 另外一个用 final 修饰):
- package com.wyq.day527;
- public class Dog{
- final public void run(String name) {
- System.out.println("狗"+name+"----run");
- }
- public void eat() {
- System.out.println("狗 ----eat");
- }
- }
方法拦截器:
- package com.wyq.day527;
- import java.lang.reflect.Method;
- import.NET.sf.cglib.proxy.MethodInterceptor;
- import.NET.sf.cglib.proxy.MethodProxy;
- public class MyMethodInterceptor implements MethodInterceptor{
- @Override
- public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
- System.out.println("这里是对目标类进行增强!!!");
- // 注意这里的方法调用, 不是用反射哦!!!
- Object object = proxy.invokeSuper(obj, args);
- return object;
- }
- }
测试类:
- package com.wyq.day527;
- import.NET.sf.cglib.core.DebuggingClassWriter;
- import.NET.sf.cglib.proxy.Enhancer;
- public class CgLibProxy {
- public static void main(String[] args) {
- // 在指定目录下生成动态代理类, 我们可以反编译看一下里面到底是一些什么东西
- System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\java\\java_workapace");
- // 创建 Enhancer 对象, 类似于 JDK 动态代理的 Proxy 类, 下一步就是设置几个参数
- Enhancer enhancer = new Enhancer();
- // 设置目标类的字节码文件
- enhancer.setSuperclass(Dog.class);
- // 设置回调函数
- enhancer.setCallback(new MyMethodInterceptor());
- // 这里的 creat 方法就是正式创建代理类
- Dog proxyDog = (Dog)enhancer.create();
- // 调用代理类的 eat 方法
- proxyDog.eat();
- }
- }
测试结果:
使用起来还是很容易的, 但是其中有很多小细节我们要注意, 下面我们就慢慢的看;
2. 生成动态代理类
首先到我们指定的目录下面看一下生成的字节码文件, 有三个, 一个是代理类的 FastClass, 一个是代理类, 一个是目标类的 FastClass, 我们看看代理类 (Dog$$EnhancerByCGLIB$$a063bd58.class), 名字略长~ 后面会仔细介绍什么是 FastClass, 这里简单说一下, 就是给每个方法编号, 通过编号找到方法, 这样可以避免频繁使用反射导致效率比较低, 也可以叫做 FastClass 机制
然后我们可以结合生成的动态代理类来简单看看原理, 上一篇说过一个反编译工具 jdGUI, 但是貌似反编译这个字节码文件会出问题, 我们可以用另外一个反编译工具 jad, 这个用起来不怎么直接.... 百度云链接: https://pan.baidu.com/s/1tDxNWlA_0Ax1JXON10U_Pg 提取码: 0zqv
我简单说说用法: 1. 必须将要反编译的字节码文件放到 jad 目录下; 2. 在 jad 目录下 shift + 鼠标右键, 选择 "在此处打开命令窗口", 也就是打开 cmd;3. 在黑窗口中输入 jad -sjava Dog$$EnhancerByCGLIB$$a063bd58.class, 就是就会以 xxx.java 的形式输出; 如果输入 jad -stxt Dog$$EnhancerByCGLIB$$a063bd58.class, 就会以 xxx.txt 的形式输出, 看你喜欢把字节码文件反编译成什么类型的...
我们就打开 xxx.java 文件, 稍微进行整理一下, 我们可以看到对于 eat 方法, 在这个代理类中对应会有 eat 和 CGLIB$eat$0 这两个方法; 其中前者则是我们使用代理类时候调用的方法, 后者是在方法拦截器里面调用的, 换句话来说当我们代码调用代理对象的 eat 方法, 然后会到方法拦截器中调用 intercept 方法, 该方法内则通过 proxy.invokeSuper 调用 CGLIB$eat$0 这个方法, 不要因为方法名字太长了就觉得难, 其实原理很简单...(顺便一提, 不知道大家有没有发现代理类中只有 eat 方法, 没有 run 方法, 因为 run 方法被 final 修饰了, 不可被重写, 所以代理类中就没有 run 方法, 这里要符合 java 规范!!!)
- package com.wyq.day527;
- import java.lang.reflect.Method;
- import.NET.sf.cglib.core.ReflectUtils;
- import.NET.sf.cglib.core.Signature;
- import.NET.sf.cglib.proxy.*;
- // 可以看到这个代理类是继承我们的目标类 Dog, 并且顺便实现了一个 Factory 接口, 这个接口就是一些设置回调函数和返回实例化对象的方法
- public class Dog$$EnhancerByCGLIB$$fbca2ec6 extends Dog implements Factory{
- // 这里有很多的属性, 仔细看一下就是一个方法对应两个, 一个是 Method 类型, 一个是 MethodProxy 类型
- private boolean CGLIB$BOUND;
- private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
- private static final Callback CGLIB$STATIC_CALLBACKS[];
- private MethodInterceptor CGLIB$CALLBACK_0;
- private static final Method CGLIB$eat$0$Method;
- private static final MethodProxy CGLIB$eat$0$Proxy;
- private static final Object CGLIB$emptyArgs[];
- private static final Method CGLIB$finalize$1$Method;
- private static final MethodProxy CGLIB$finalize$1$Proxy;
- private static final Method CGLIB$equals$2$Method;
- private static final MethodProxy CGLIB$equals$2$Proxy;
- private static final Method CGLIB$toString$3$Method;
- private static final MethodProxy CGLIB$toString$3$Proxy;
- private static final Method CGLIB$hashCode$4$Method;
- private static final MethodProxy CGLIB$hashCode$4$Proxy;
- private static final Method CGLIB$clone$5$Method;
- private static final MethodProxy CGLIB$clone$5$Proxy;
- // 静态代码块, 调用下面静态方法, 这个静态方法大概做的就是获取目标方法中每个方法的 MethodProxy 对象
- static {
- CGLIB$STATICHOOK1();
- }
- // 无参构造器
- public Dog$$EnhancerByCGLIB$$fbca2ec6()
- {
- CGLIB$BIND_CALLBACKS(this);
- }
- // 此方法在上面的静态代码块中被调用
- static void CGLIB$STATICHOOK1(){
- // 注意下面这两个 Method 数组, 用于保存反射获取的 Method 对象, 避免每次都用反射去获取 Method 对象
- Method[] amethod;
- Method[] amethod1;
- CGLIB$THREAD_CALLBACKS = new ThreadLocal();
- CGLIB$emptyArgs = new Object[0];
- // 获取目标类的字节码文件
- Class class1 = Class.forName("com.wyq.day527.Dog$$EnhancerByCGLIB$$fbca2ec6");
- // 代理类的字节码文件
- Class class2;
- //ReflectUtils 是一个包装各种反射操作的工具类, 通过这个工具类来获取各个方法的 Method 对象, 然后保存到上述的 Method 数组中
- amethod = ReflectUtils.findMethods(new String[] {
- "finalize", "()V", "equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"
- }, (class2 = Class.forName("java.lang.Object")).getDeclaredMethods());
- Method[] _tmp = amethod;
- // 为目标类的每一个方法都建立索引, 可以想象成记录下来目标类中所有方法的地址, 需要用调用目标类方法的时候根据地址就能直接找到该方法
- // 这就是此处 CGLIB$xxxxxx$$Proxy 的作用...
- CGLIB$finalize$1$Method = amethod[0];
- CGLIB$finalize$1$Proxy = MethodProxy.create(class2, class1, "()V", "finalize", "CGLIB$finalize$1");
- CGLIB$equals$2$Method = amethod[1];
- CGLIB$equals$2$Proxy = MethodProxy.create(class2, class1, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$2");
- CGLIB$toString$3$Method = amethod[2];
- CGLIB$toString$3$Proxy = MethodProxy.create(class2, class1, "()Ljava/lang/String;", "toString", "CGLIB$toString$3");
- CGLIB$hashCode$4$Method = amethod[3];
- CGLIB$hashCode$4$Proxy = MethodProxy.create(class2, class1, "()I", "hashCode", "CGLIB$hashCode$4");
- CGLIB$clone$5$Method = amethod[4];
- CGLIB$clone$5$Proxy = MethodProxy.create(class2, class1, "()Ljava/lang/Object;", "clone", "CGLIB$clone$5");
- amethod1 = ReflectUtils.findMethods(new String[] {
- "eat", "()V"
- }, (class2 = Class.forName("com.wyq.day527.Dog")).getDeclaredMethods());
- Method[] _tmp1 = amethod1;
- CGLIB$eat$0$Method = amethod1[0];
- CGLIB$eat$0$Proxy = MethodProxy.create(class2, class1, "()V", "eat", "CGLIB$eat$0");
- }
- // 这个方法就是调用目标类的的 eat 方法
- final void CGLIB$eat$0()
- {
- super.eat();
- }
- // 这个方法是我们是我们要调用的, 在前面的例子中调用代理对象的 eat 方法就会到这个方法中
- public final void eat(){
- //CGLIB$CALLBACK_0 = (MethodInterceptor)callback;
- CGLIB$CALLBACK_0;
- // 这里就是判断 CGLIB$CALLBACK_0 是否为空, 也就是我们传入的方法拦截器是否为空, 如果不为空就最终到下面的_L4
- if(CGLIB$CALLBACK_0 != null) goto _L2; else goto _L1
- _L1:
- JVM INSTR pop ;
- CGLIB$BIND_CALLBACKS(this);
- CGLIB$CALLBACK_0;
- _L2:
- JVM INSTR dup ;
- JVM INSTR ifnull 37;
- goto _L3 _L4
- _L3:
- break MISSING_BLOCK_LABEL_21;
- _L4:
- break MISSING_BLOCK_LABEL_37;
- this;
- CGLIB$eat$0$Method;
- CGLIB$emptyArgs;
- CGLIB$eat$0$Proxy;
- // 这里就是调用方法拦截器的 intecept() 方法
- intercept();
- return;
- super.eat();
- return;
- }
- // 这里省略 finalize,equals,toString,hashCode,clone, 因为和上面的 eat 的两个方法差不多
- //..........
- //...........
- //..........
- public static MethodProxy CGLIB$findMethodProxy(Signature signature)
- {
- String s = signature.toString();
- s;
- s.hashCode();
- JVM INSTR lookupswitch 6: default 140
- // -1574182249: 68
- // -1310345955: 80
- // -508378822: 92
- // 1826985398: 104
- // 1913648695: 116
- // 1984935277: 128;
- goto _L1 _L2 _L3 _L4 _L5 _L6 _L7
- _L2:
- "finalize()V";
- equals();
- JVM INSTR ifeq 141;
- goto _L8 _L9
- _L9:
- break MISSING_BLOCK_LABEL_141;
- _L8:
- return CGLIB$finalize$1$Proxy;
- _L3:
- "eat()V";
- equals();
- JVM INSTR ifeq 141;
- goto _L10 _L11
- _L11:
- break MISSING_BLOCK_LABEL_141;
- _L10:
- return CGLIB$eat$0$Proxy;
- _L4:
- "clone()Ljava/lang/Object;";
- equals();
- JVM INSTR ifeq 141;
- goto _L12 _L13
- _L13:
- break MISSING_BLOCK_LABEL_141;
- _L12:
- return CGLIB$clone$5$Proxy;
- _L5:
- "equals(Ljava/lang/Object;)Z";
- equals();
- JVM INSTR ifeq 141;
- goto _L14 _L15
- _L15:
- break MISSING_BLOCK_LABEL_141;
- _L14:
- return CGLIB$equals$2$Proxy;
- _L6:
- "toString()Ljava/lang/String;";
- equals();
- JVM INSTR ifeq 141;
- goto _L16 _L17
- _L17:
- break MISSING_BLOCK_LABEL_141;
- _L16:
- return CGLIB$toString$3$Proxy;
- _L7:
- "hashCode()I";
- equals();
- JVM INSTR ifeq 141;
- goto _L18 _L19
- _L19:
- break MISSING_BLOCK_LABEL_141;
- _L18:
- return CGLIB$hashCode$4$Proxy;
- _L1:
- JVM INSTR pop ;
- return null;
- }
- public static void CGLIB$SET_THREAD_CALLBACKS(Callback acallback[])
- {
- CGLIB$THREAD_CALLBACKS.set(acallback);
- }
- public static void CGLIB$SET_STATIC_CALLBACKS(Callback acallback[])
- {
- CGLIB$STATIC_CALLBACKS = acallback;
- }
- private static final void CGLIB$BIND_CALLBACKS(Object obj)
- {
- Dog$$EnhancerByCGLIB$$fbca2ec6 dog$$enhancerbycglib$$fbca2ec6 = (Dog$$EnhancerByCGLIB$$fbca2ec6)obj;
- if(dog$$enhancerbycglib$$fbca2ec6.CGLIB$BOUND) goto _L2; else goto _L1
- _L1:
- Object obj1;
- dog$$enhancerbycglib$$fbca2ec6.CGLIB$BOUND = true;
- obj1 = CGLIB$THREAD_CALLBACKS.get();
- obj1;
- if(obj1 != null) goto _L4; else goto _L3
- _L3:
- JVM INSTR pop ;
- CGLIB$STATIC_CALLBACKS;
- if(CGLIB$STATIC_CALLBACKS != null) goto _L4; else goto _L5
- _L5:
- JVM INSTR pop ;
- goto _L2
- _L4:
- (Callback[]);
- dog$$enhancerbycglib$$fbca2ec6;
- JVM INSTR swap ;
- 0;
- JVM INSTR aaload ;
- (MethodInterceptor);
- CGLIB$CALLBACK_0;
- _L2:
- }
- public Object newInstance(Callback acallback[])
- {
- CGLIB$SET_THREAD_CALLBACKS(acallback);
- CGLIB$SET_THREAD_CALLBACKS(null);
- return new Dog$$EnhancerByCGLIB$$fbca2ec6();
- }
- public Object newInstance(Callback callback)
- {
- CGLIB$SET_THREAD_CALLBACKS(new Callback[] {
- callback
- });
- CGLIB$SET_THREAD_CALLBACKS(null);
- return new Dog$$EnhancerByCGLIB$$fbca2ec6();
- }
- public Object newInstance(Class aclass[], Object aobj[], Callback acallback[])
- {
- CGLIB$SET_THREAD_CALLBACKS(acallback);
- JVM INSTR new #2 <Class Dog$$EnhancerByCGLIB$$fbca2ec6>;
- JVM INSTR dup ;
- aclass;
- aclass.length;
- JVM INSTR tableswitch 0 0: default 35
- // 0 28;
- goto _L1 _L2
- _L2:
- JVM INSTR pop ;
- Dog$$EnhancerByCGLIB$$fbca2ec6();
- goto _L3
- _L1:
- JVM INSTR pop ;
- throw new IllegalArgumentException("Constructor not found");
- _L3:
- CGLIB$SET_THREAD_CALLBACKS(null);
- return;
- }
- public Callback getCallback(int i)
- {
- CGLIB$BIND_CALLBACKS(this);
- this;
- i;
- JVM INSTR tableswitch 0 0: default 30
- // 0 24;
- goto _L1 _L2
- _L2:
- CGLIB$CALLBACK_0;
- goto _L3
- _L1:
- JVM INSTR pop ;
- null;
- _L3:
- return;
- }
- public void setCallback(int i, Callback callback)
- {
- switch(i)
- {
- case 0: // '\0'
- CGLIB$CALLBACK_0 = (MethodInterceptor)callback;
- break;
- }
- }
- public Callback[] getCallbacks()
- {
- CGLIB$BIND_CALLBACKS(this);
- this;
- return (new Callback[] {
- CGLIB$CALLBACK_0
- });
- }
- public void setCallbacks(Callback acallback[])
- {
- this;
- acallback;
- JVM INSTR dup2 ;
- 0;
- JVM INSTR aaload ;
- (MethodInterceptor);
- CGLIB$CALLBACK_0;
- }
- }
- View Code
根据上面的代码我们可以知道代理类中主要有几部分组成: 1. 重写的父类方法, 2.CGLIB$eat$0 这种奇怪的方法, 3.Interceptor() 方法, 4.newInstance 和 get/setCallback 方法
3.FastClass 机制分析
为什么要用这种机制呢? 直接用反射多好啊, 但是我们知道反射虽然很好用, 但是和直接 new 对象相比, 效率有点慢, 于是就有了这种机制, 我参考这篇博客 https://www.cnblogs.com/cruze/p/3865180.html, 有个小例子可以说的非常清楚;
- public class test10 {
- // 这里, tt 可以看作目标对象, fc 可以看作是代理对象; 首先根据代理对象的 getIndex 方法获取目标方法的索引,
- // 然后再调用代理对象的 invoke 方法就可以直接调用目标类的方法, 避免了反射
- public static void main(String[] args){
- Test tt = new Test();
- Test2 fc = new Test2();
- int index = fc.getIndex("f()V");
- fc.invoke(index, tt, null);
- }
- }
- class Test{
- public void f(){
- System.out.println("f method");
- }
- public void g(){
- System.out.println("g method");
- }
- }
- class Test2{
- public Object invoke(int index, Object o, Object[] ol){
- Test t = (Test) o;
- switch(index){
- case 1:
- t.f();
- return null;
- case 2:
- t.g();
- return null;
- }
- return null;
- }
- // 这个方法对 Test 类中的方法建立索引
- public int getIndex(String signature){
- switch(signature.hashCode()){
- case 3078479:
- return 1;
- case 3108270:
- return 2;
- }
- return -1;
- }
- }
在 CGLib 的代理类中, 生成 FastClass 相关代码如下;
- Class class1 = Class.forName("com.wyq.day527.Dog$$EnhancerByCGLIB$$fbca2ec6");
- Class class2 = Class.forName("com.wyq.day527.Dog")).getDeclaredMethods()
- CGLIB$eat$0$Proxy = MethodProxy.create(class2, class1, "()V", "eat", "CGLIB$eat$0");
4. 简单原理
上面我们看了 CGLib 动态代理的用法, 实际生成的代理类以及 FastClass 机制, 下面我们就以最前面的那个例子中调用 eat() 方法来看看主要的调用步骤;
第一步: 是经过一系列操作实例化出了 Enhance 对象, 并设置了所需要的参数然后 enhancer.create() 成功创建出来了代理对象, 这个就不多说了...
第二步: 调用代理对象的 eat() 方法, 会进入到方法拦截器的 intercept() 方法, 在这个方法中会调用 proxy.invokeSuper(obj, args); 方法
第三步: invokeSuper 中, 通过 FastClass 机制调用目标类的方法
方法拦截器中只有一个 invoke 方法, 这个方法有四个参数, obj 表示代理对象, method 表示目标类中的方法, args 表示方法参数, proxy 表示代理方法的 MethodProxy 对象
在这个方法内部会调用 proxy.invokeSuper(obj, args) 方法, 我们进入. invokeSuper 方法内部看看:
简单看看 init() 方法:
FastClassInfo 内部如下图, 由此可以看出 prxy.invokeSuper() 方法中 fci.f2.invoke(fci.i2, obj, args), 其实就是调用 CGLIB$eat$ 这个方法
invoke 方法是个抽象方法, 我们反编译一下代理类的 FastClass(也就是生成的那三个字节码文件名称最长的那个) 就可以看到, 由于代码比较长, 就不复制了...
5. 总结
CGLib 动态代理是将继承用到了极致, 我们这里也就是简单的看了看, 没有怎么深入, 想深入了解的可以自己查查资料... 感觉暂时到这里就差不多了, 以后用到的话再继续挖掘! 对于一个新的东西, 不要想着一下子全部弄懂, 因为太吃力了, 一口吃不成胖子, 要先弄懂一点, 然后慢慢深入即可!
这里随便画一个简单的图看看整个过程, 当我们去调用方法一的时候, 在代理类中会先判断是否实现了方法拦截的接口, 没实现的话直接调用目标类的方法一; 如果实现了那就会被方法拦截器拦截, 在方法拦截器中会对目标类中所有的方法建立索引, 其实大概就是将每个方法的引用保存在数组中, 我们就可以根据数组的下标直接调用方法, 而不是用反射; 索引建立完成之后, 方法拦截器内部就会调用 invoke 方法 (这个方法在生成的 FastClass 中实现), 在 invoke 方法内就是调用 CGLIB$ 方法一 $ 这种方法, 也就是调用对应的目标类的方法一;
一般我们要添加自己的逻辑就是在方法拦截器那里....
来源: https://www.cnblogs.com/wyq1995/p/10945034.html