Spring 5.x 源码之旅-79事务CGLIB动态代理的秘密一

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO

联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬

学习必须往深处挖,挖的越深,基础越扎实!

阶段1、深入多线程

阶段2、深入多线程设计模式

阶段3、深入juc源码解析


阶段4、深入jdk其余源码解析


阶段5、深入jvm源码解析

 

码哥源码部分

码哥讲源码-原理源码篇【2024年最新大厂关于线程池使用的场景题】

码哥讲源码【炸雷啦!炸雷啦!黄光头他终于跑路啦!】

码哥讲源码-【jvm课程前置知识及c/c++调试环境搭建】

 

​​​​​​码哥讲源码-原理源码篇【揭秘join方法的唤醒本质上决定于jvm的底层析构函数】

码哥源码-原理源码篇【Doug Lea为什么要将成员变量赋值给局部变量后再操作?】

码哥讲源码【你水不是你的错,但是你胡说八道就是你不对了!】

码哥讲源码【谁再说Spring不支持多线程事务,你给我抽他!】

终结B站没人能讲清楚红黑树的历史,不服等你来踢馆!

打脸系列【020-3小时讲解MESI协议和volatile之间的关系,那些将x86下的验证结果当作最终结果的水货们请闭嘴】

 

CGLIB代理方法执行基本流程

CGLIB动态代理的细节

本篇想讲下CGLIB动态代理的一些细节,因为前面的事务讲的是JDK的,我们知道JDK动态代理最终就是用了方法的反射调用,调用被代理对象的方法,而CGLIB可不是那么简单,虽然最终是执行了代理对象的方法,但是不是反射哦,而是用了方法代理MethodProxyFastClass来做的,这个是什么鬼,我们慢慢道来。

来个例子讲比较好理解

这个例子虽然只用了拦截器,拦截器怎么用的,过滤器索引怎么对应过滤器的这个就不讲了,百度就好了,我要讲里面的细节,要展示的秘密可是很多的哦,我们来看看。

    public class CglibObj {
        public void f1() {
            System.out.println("f1");
        }
    
        public void f2() {
            System.out.println("f2");
        }
    }
    
    
    public class Test {
        public static void main(String[] args) {
        	//把CGLIB生成的字节码文件保存到本地D:\cglib,到时候可以拖进idea里看
            System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\cglib");
            MethodInterceptor m1 = new MethodInterceptor() {
                @Override
                public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                    System.out.println("拦截器1 before");
                    methodProxy.invokeSuper(o, objects);
                    System.out.println("拦截器1 after");
                    return null;
                }
            };
            MethodInterceptor m2 = new MethodInterceptor() {
                @Override
                public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                    System.out.println("拦截器2 before");
                    methodProxy.invokeSuper(o, objects);
                    System.out.println("拦截器2 after");
                    return null;
                }
            };
            //返回索引对应Callback数组里的拦截器索引
            CallbackFilter callbackFilter = method -> {
                if (method.getName().equals("f1"))
                    return 0;
                else if (method.getName().equals("f2"))
                    return 1;
                return 2;
            };
    
            CglibObj cglibObj1 = (CglibObj) Enhancer.create(CglibObj.class, null, callbackFilter, new Callback[]{m1, m2,NoOp.INSTANCE});
            cglibObj1.f1();
            cglibObj1.f1();
            cglibObj1.f2();
            CglibObj cglibObj2 = (CglibObj) Enhancer.create(CglibObj.class, null, callbackFilter, new Callback[]{m1, m2,NoOp.INSTANCE});
            cglibObj2.f1();
            cglibObj2.f2();
        }

cglibObj1.f1()怎么执行的

我们直接debug


直接进了拦截器里面了:

增强类

这个是怎么回事呢,其实内部有生成一个代理类的,我们能看到,这个就是之所以最开始把字节码文件保存到本地的原因了,此时有2个文件,记得数量,后面会变哦:


我们看到一个是EnhancerByCGLIB增强类,另外一个是FastClassByCGLIB的增强类,他们是什么呢。

CGLIB动态代理类

我们拖进来分析:


EnhancerByCGLIB就是代理类,也就是用方法拦截器的类,我们来稍微看下里面的内容,我们可以看到这个类是继承我们的CglibObj,还有个接口是spring的,暂时不用管,可以看到有3个拦截器,前面2个是我们自定义的,后面是什么都不干的拦截器,还有2个方法代理还2个原方法,方法代理就是后面会利用FastClass来调用方法的,后面会说:


从外面也可以看到拦截器:

方法增强

首先这个类初始化的时候会把属性都初始化了:


获得被代理类的原方法和创建了方法代理MethodProxy,这里会有CGLIB$f1$0,CGLIB$f2$1来表示对应的代理对象的f1,f2的别名,后面获取方法索引会有用:

    static void CGLIB$STATICHOOK1() {
            CGLIB$THREAD_CALLBACKS = new ThreadLocal();
            CGLIB$emptyArgs = new Object[0];
            Class var0 = Class.forName("com.ww.proxy.CglibObj$$EnhancerByCGLIB$$ef630afc");
            Class var1;
            Method[] var10000 = ReflectUtils.findMethods(new String[]{"f1", "()V", "f2", "()V"}, (var1 = Class.forName("com.ww.proxy.CglibObj")).getDeclaredMethods());
            CGLIB$f1$0$Method = var10000[0];//原f1
            //代理f1
            CGLIB$f1$0$Proxy = MethodProxy.create(var1, var0, "()V", "f1", "CGLIB$f1$0");
            CGLIB$f2$1$Method = var10000[1];//原f2
            //代理f2
            CGLIB$f2$1$Proxy = MethodProxy.create(var1, var0, "()V", "f2", "CGLIB$f2$1");
        }
f1/f2增强后

首先会获取拦截器,然后如果存在的话就执行intercept方法,否则就是父类的方法,也就是原方法:


获取拦截器:


调用拦截器:


参数是对应的,代理对象,原方法,方法参数,方法代理MethodProxy


这里要注意这里的方法和代理方法是静态的,也就是说同一个类的不同实例调用的方法和代理方法是共享的,所以这里可以提供性能,不用每次都去获取方法,创建方法代理MethodProxy :

现在我们知道CGLIB是怎么调用拦截器了吧,会创建一个字节码增强类,里面会获取原方法,创建代理方法,然后覆盖原方法,里面获取拦截器,调用拦截器的intercept方法。当然拦截器里面怎么调用也有讲究,否则可能会无限循环调用,还有个FastClassByCGLIB还没说,这个是优化方法调用的,后面说。

  • 38
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值