用通俗易懂的语言去解释cglib的动态代理

读完本篇文章将会了解以下问题

1.cglib是什么

2.cglib能做什么

3.cglib如何使用

4.cglib底层实现原理

5.为什么Spring中方法嵌套会导致事务失效

---------------------------------------------------------------------------------------------------------------------------

1.cglib是什么

       Code Generation Library:代码生成库,用来在内存中动态生成字节码文件,又叫字节码增强,底层使用ASM(java字节码操纵框架),它在从类文件中读入信息后,可以根据用户的要求生成新的类(动态代理类)。

2.cglib能做什么

        用来实现动态代理(废话。。。),比如Spring中的@Transactional事务注解,打在方法上就可以让方法具有事务的属性,支持回滚操作。盲猜一下他的底层实现就是通过代理,把我们以前手动写的开启事务啊,失败回滚啊,成功就提交啊等代码增强到对应方法中。再比如@Cacheable缓存注解,打在某一个方法上,Spring就可以把这个方法的返回值进行缓存,再猜一下,就是在调用这个方法之前,插入一段查找缓存的代码,执行方法之后,判断如果缓存内没有这个值,就将该方法的返回值放入缓存,进而实现对打了@Cacheable注解的方法缓存。

3.cglib如何使用

3.1 创建被代理对象(不用像JDK动态代理那样实现一个接口了)

package cglib.user;
public class GiaoGiao {
    public String str = "str";
    public GiaoGiao() {
        System.out.println("构造Giao哥");
    }
    /**
     * 该方法不能被子类覆盖,Cglib无法代理final修饰的方法
     */
    final public String sing(String name) {
        System.out.println("Giao哥:开始唱"+name);
        return "giao哥唱完了" ;
    }
    public void dance() {
        System.out.println("Giao哥:边唱边giao");
    }
}

3.2 实现MethodInterceptor接口

package cglib.user;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class MyMethodInterceptor implements MethodInterceptor {
    /**
     * sub:cglib生成的代理对象
     * method:被代理对象方法
     * objects:方法入参
     * methodProxy: 代理方法
     */
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("======插入前置通知======");
        Object object = methodProxy.invokeSuper(o, objects);
        System.out.println("======插入后置通知======");
        return object;
    }
}

3.3 使用构建增强器Enhancer创建代理对象,调用目标方法

package cglib.run;
import cglib.user.GiaoGiao;
import cglib.user.MyMethodInterceptor;
import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.proxy.Enhancer;
import org.objenesis.ObjenesisStd;
public class run {
    public static void main(String[] args) {
        // 代理类class文件存入本地磁盘方便我们反编译查看源码
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\cglibCode");
        // 通过CGLIB动态代理获取代理对象的过程
        Enhancer enhancer = new Enhancer();
        // 设置enhancer对象的父类
        enhancer.setSuperclass(GiaoGiao.class);
        // 设置enhancer的回调对象
        enhancer.setCallback(new MyMethodInterceptor());
        //方式1:通过Enhancer创建代理对象
        GiaoGiao proxy= (GiaoGiao)enhancer.create();
        //方式2:通过ObjenesisStd+cglib创建代理对象
        //ObjenesisStd objenesisStd = new ObjenesisStd(true);
        //GiaoGiao proxy= (GiaoGiao)objenesisStd.newInstance(GiaoGiao.class);
        System.out.println(proxy.str);
        // 通过代理对象调用目标方法
        proxy.sing("lalala");
        proxy.dance();
    }
}

输出结果:

构造Giao哥
str
Giao哥:开始唱lalala
======插入前置通知======
Giao哥:边唱边giao
======插入后置通知======

       这里要注意两点:

       1、cglib采用继承的方式实现动态代理,因为父类中标记有final的方法无法被子类继承,所以无法利用cglib做方法增强

       2、cglib创建代理对象有两种方式,如果采用ObjenesisStd+cglib这种方式构建代理对象的话需要注意一个问题,就是Objenesis不会去执行被代理类的构造方法,其会绕过父类构造器直接产生代理类(子类)对象,所以上述例子中如果切换成方式2去生成代理对象的话,父类中的str变量就会为null,因为其成员变量根本就没有被初始化。

4.cglib底层实现原理

       这里要吐槽一句,看cglib源码真的很痛苦,因为它内部无论是类名还是变量根本没有做到望文知意,生成的动态代理类可能本来就没想给别人看。这里只分析第一种创建方式,通过Enhancer创建代理对象的整体流程。前面分析过JDK的动态代理,有兴趣的童鞋可以看下我的这篇文章,《用通俗易懂的语言去解释JDK的动态代理》,现在来分析cglib的动态代理也从那两个经典问题入手:

       1.cglib是如何生成代理类目标对象并初始化的?(底层用ASM生成字节码文件,然后用反射生成实例对象,这里我就不分析了,也确实分析不出来,看了半天源码没搞懂。。。)

       2.cglib生成的代理类是如何实现对被代理类方法进行增强的?(就主要聊聊这个问题吧)

       好的,main方法第一行可以将代理类class文件保存到磁盘上,进文件夹一看,好的,搞出来三个class

       我们看名字,这三个类中有两个FastClass类,第一个类为代理类的FastClass类,第二个类为代理类,里面保存着被代理类的信息,第三个类为被代理类的FastClass类。我的理解是这两个FastClass类都是为我们的代理类服务的,他们俩保存着和被代理类的关系数据(方法名,方法参数,被代理对象),最终都是为了可以让代理类实现增强的功能而存在的。我们先看第二个类动态代理类的class源码:

public class GiaoGiao$$EnhancerByCGLIB$$5627d2d6 extends GiaoGiao implements Factory {
    ...
    private MethodInterceptor CGLIB$CALLBACK_0;
    private static final Method CGLIB$dance$0$Method;
    private static final MethodProxy CGLIB$dance$0$Proxy;
    private static final MethodProxy CGLIB$finalize$1$Proxy;
    private static final MethodProxy CGLIB$equals$2$Proxy;
    private static final MethodProxy CGLIB$toString$3$Proxy;
    private static final MethodProxy CGLIB$hashCode$4$Proxy;
    private static final MethodProxy CGLIB$clone$5$Proxy;
    static void CGLIB$STATICHOOK1() {
        CGLIB$THREAD_CALLBACKS = new ThreadLocal();
        CGLIB$emptyArgs = new Object[0];
        Class var0 = Class.forName("cglib.user.GiaoGiao$$EnhancerByCGLIB$$5627d2d6");
        Class var1;
        Method[] var10000 = ReflectUtils.findMethods(new String[]{"finalize", "()V", "equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods());
        CGLIB$finalize$1$Method = var10000[0];
        CGLIB$finalize$1$Proxy = MethodProxy.create(var1, var0, "()V", "finalize", "CGLIB$finalize$1");
        CGLIB$equals$2$Method = var10000[1];
        CGLIB$equals$2$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$2");
        CGLIB$toString$3$Method = var10000[2];
        CGLIB$toString$3$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$3");
        CGLIB$hashCode$4$Method = var10000[3];
        CGLIB$hashCode$4$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$4");
        CGLIB$clone$5$Method = var10000[4];
        CGLIB$clone$5$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$5");
        CGLIB$dance$0$Method = ReflectUtils.findMethods(new String[]{"dance", "()V"}, (var1 = Class.forName("cglib.user.GiaoGiao")).getDeclaredMethods())[0];
        CGLIB$dance$0$Proxy = MethodProxy.create(var1, var0, "()V", "dance", "CGLIB$dance$0");
    }
    final void CGLIB$dance$0() {
        super.dance();
    }
    public final void dance() {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }
        if (var10000 != null) {
            var10000.intercept(this, CGLIB$dance$0$Method, CGLIB$emptyArgs, CGLIB$dance$0$Proxy);
        } else {
            super.dance();
        }
    }
    ...
    static {
        CGLIB$STATICHOOK1();
    }
}

       我们可以看到该类首先有一个静态代码块,代码块里有一个钩子函数负责对该类中一大堆的成员属性进行初始化。然后还有一个 成员属性MethodInterceptor CGLIB$CALLBACK_0;这个成员属性是在我们设置enhancer的回调对象时传入的,同时我们往下看dance方法里面是由var10000这个变量调用的intercept方法,而这个var10000就是我们设置的回调对象,也就是在这里实现了跳转回MyMethodInterceptor类中我们编写增强代码的intercept方法内。那么剩下的问题就是:

Object object = methodProxy.invokeSuper(o, objects);

       这一行代码是怎么实现调用被代理类的目标方法的呢?我们看到在这个代理类里还有一个CGLIB$dance$0()方法,方法体就是调用父类dance()方法,而代理类的父类就是我们的被代理类。那现在问题又变成了,如何在调用methodProxy.invokeSuper(o, objects);方法的时候,让其调用CGLIB$dance$0()方法呢?我们在后置通知打个断点看一下各个变量的值:

       o:没啥好说的了代理类

       method:目标方法

       methodProxy:重点是这个变量里面的fastClassInfo实体,f1为被代理类的fastClass对象,f2为代理类的fastClass对象,i1没用到,用到的i2:20,记住这个数。

       这个方法跟进去为:

      看一下init()方法

       上面那些是对两个fastClass类进行赋值,重点是这两个getIndex(),我们看一下代理类fastClass类对象的getIndex()方法:

       这是在做啥呢,原来cglib是采用建立方法索引来返回对应方法的位置,上图为代理类fastClass对象dance()方法的下标。

     上图为被代理类fastClass对象dance()方法的下标,到此我们终于知道这俩个int的值是干啥的了。接上文:

 fci:就是上面methodProxy类中的fastClassInfo实体,f2为上面的代理类的fastClass对象,i2为20

public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
        a5232984 var10000 = (a5232984)var2;
        int var10001 = var1;
        try {
            switch(var10001) {
            case 0:
                return new Boolean(var10000.equals(var3[0]));
            ...
            case 20:
                var10000.CGLIB$dance$0();
                return null;
            }
        } catch (Throwable var4) {
            throw new InvocationTargetException(var4);
        }
        throw new IllegalArgumentException("Cannot find matching method/constructor");
    }

       a5232984 var10000 = (a5232984)var2;

       把object对象强转为代理类对象,5232984为代理类编号(姑且暂时这么叫它,我自己起的名字)

       var1为传进来的20,case 20对应的就是代理类的CGLIB$dance$0()方法,而代理类的这个方法:

final void CGLIB$dance$0() {
        super.dance();
}

       它的super就是我们的被代理类

public class GiaoGiao$$EnhancerByCGLIB$$a5232984 extends GiaoGiao implements Factory {

}

5.为什么Spring中方法嵌套会导致事务失效

       如果我们了解了cglib整个代理流程,那么这个问题就很容易分析了。Spring中两个均加了事务注解的方法A和B,在A方法中调用B方法,会导致B的事务不起作用,这是因为Spring默认采用的就是cglib动态代理,方法A其实是动态生成的代理类在执行,而在A方法中调用B方法的时候,默认的是this.B();因为代理对象把A方法当做了目标方法,把调用A方法的service当成了被代理类,那么B方法对于A的代理对象来说就是一个普通的方法,并没有使用代理类来执行,所以不会享受代理的增强,事务也就不会生效了。

       大功告成,收工收工。cglib源码花了我好几天时间,脑壳都搞痛了。。。

  • 9
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值