Spring5 底层原理 aop之cglib代理原理(黑马)

cglib 代理

CGLIB代理是一种基于继承的代理方式,它可以在运行时动态生成代理类,代理类是目标类的一个子类,重写了目标类的方法,实现了对目标类的方法增强

模拟 cglib 代理

Target(用来做增强的方法)
在这里插入图片描述

1.创建Porxy类模拟真实代码

Porxy类是目标类的子类,重写了目标类的方法,实现了对目标类的方法增强

在这里插入图片描述

2.定义一个MethodInterceptor

**MethodInterceptor**类似于JDK动态代理的**InvocationHandler,通过set方法或者构造器注入**

在这里插入图片描述

3.在重写的方法中调用MethodInterceptor的intercept方法

这里是需要的一些参数:

  • Object o:代理类对象(在这里本身为代理所以为this)
  • Method method:当前执行的方法(方法对象只需要一次,我们定义为static)
  • Object [ ] objects:当前执行方法的参数(看有无参数,无参new一个空数组,有参就new一个有值的数组)
  • MethodProxy methodProxy:CGLIB库中的一个类,用于代表目标类中的一个方法 (第四个这里后面讲,先传null)
    在这里插入图片描述
    静态方法对象,在静态代码块中初始化
    在这里插入图片描述
    框里对应:代理对象、方法对象、方法参数(这里无参)、methodProxy。
    在这里插入图片描述
    下面两个方法笔者认为大家都能分析就不罗嗦了。在这里插入图片描述

4.1这里用的是反射调用

测试类中可以试一下:
代码从上到下依次为创建代理对象、目标对象、注入MethodInterceptor、打印模拟前置增强、method方法对象反射调用被增强方法、利用proxy代理对象调用测试结果。
在这里插入图片描述
结果如下(都进行了前置打印增强):
在这里插入图片描述

4.2下面来讲一下methodProxy如何避免反射调用的

如何创建methodProxy对象

  • 在Proxy中新增带原始功能的方法(没增强的方法,增强的方法就是上文第一次重写的save方法)
    在这里插入图片描述
  • 创建MethodProxy对象(与方法对象类似在静态代码块初始化,利用MethodProxy里的create方法创建出来)
    在这里插入图片描述
  • 参数如下(这里用无参的save生成MethodProxy演示):
    • Class c1:目标对象(Target.class)
    • Class c2:代理对象(Proxy.class)
    • String desc:这里类似java字节码中的表示,偏底层(无参的这样写,第一个括号表示无参,V表示返回void ------> ( )V
    • String name1:增强功能的方法名( save )
    • String name2:不带增强功能的方法名( saveSuper )
      在这里插入图片描述下面两个MethodProxy笔者就省略精简一点(第一个desc的阔号中的字母 I 表示参数为int类型,为 J 的表示参数为 long 类型 )。
      在这里插入图片描述
      最后把上文设置为null的MethodProxy传进去,回到测试类测试打印结果:

这个是结合目标用的( methdoProxy.invoke(target, args) ):
在这里插入图片描述
这个是结合目标用的,也就是说我们不用创捷Target的实例也能正常执行( methodProxy.invokeSuper(o, args); ):
在这里插入图片描述
结果如下(二者结果相同):
在这里插入图片描述

收获

和 jdk 动态代理原理查不多

  1. 回调的接口换了一下,InvocationHandler 改成了 MethodInterceptor
  2. 调用目标时有所改进,见下面代码片段
    1. method.invoke 是反射调用,必须调用到足够次数才会进行优化
    2. methodProxy.invoke 是不反射调用,它会正常(间接)调用目标对象的方法(Spring 采用)
    3. methodProxy.invokeSuper 也是不反射调用,它会正常(间接)调用代理对象的方法,可以省略目标对象

methodProxy原理

在methodProxy的上述两个方法中又会产生各自的代理类配合其使用,由于我们已经有一个代理Porxy来增强方法,后面俩个代理的父类叫FastClass所以我们为了分开表达,这里methodProxy方法中生成的代理我们就称作FastClass,目的为了区分增强代理与避免反射的代理。

在FastClass源码中我们发现有6个抽象方法,这里笔者只说两个关键的方法getIndex、invoke
在这里插入图片描述

模拟FastClass实现

由于FastClass中编译的步骤是直接生成字节码,看源码不太方便,即使拿到了源码逻辑也比较复杂难懂,这里我们模拟实现

创建两个用于避免反射调用的两个FastClass子类

本来我们是要让子类继承FastClass,但是里面抽象方法只说两个,所以这里挑两个发法模拟

  • 第一个方法作用是获取目标方法的一个编号,参数为 signature(翻译为:签名) 包括方法名字、参数返回值
  • 第二个方法作用是根据第一个方法返回的编号 正常调用 目标对象的方法

我们Proxy中调用invoke方法时,内部就会调用到FastClass的invok方法

在这里插入图片描述

配合目标用的笔者取类名为TargetFastClass
在这里插入图片描述
其实在我们创建methodProxy时就会生成我们的FastClass,自然每个生成的methodProxy都知道自己的签名,因为下面生成methodProxy传入的desc与name1就为该方法的signature

在这里插入图片描述
这里再源码中生成FastClass的时候会将这些签名定义好,同样以static修饰
在这里插入图片描述
这时方法签名定义好了以后,在getIndex中就可以判断这个签名对应哪个方法

 // 获取目标方法的编号
    /*
        Target
            save()              0
            save(int)           1
            save(long)          2
        signature 包括方法名字、参数返回值
     */

在这里插入图片描述
当我们调用methodProxy时,参数里包含签名,就可以根据签名找到对应方法的编号,就会根据编号正常调用方法(在这里我们创建methodProxy时,也将目标已经参数传递过来,再转换一下类型即可调用)
在这里插入图片描述
我们再方法内测试一下:
在这里插入图片描述
结果:
在这里插入图片描述

配合目标用的笔者取类名为ProxyFastClass

这里我们将save换为saveSuper、invoke中传进来的参数由Target改为Proxy即可,原因时我们利用Proxy代理中正常调用的方法来实现调用


import org.springframework.cglib.core.Signature;

public class ProxyFastClass {
    static Signature s0 = new Signature("saveSuper", "()V");
    static Signature s1 = new Signature("saveSuper", "(I)V");
    static Signature s2 = new Signature("saveSuper", "(J)V");

    // 获取代理方法的编号
    /*
        Proxy
            saveSuper()              0
            saveSuper(int)           1
            saveSuper(long)          2
        signature 包括方法名字、参数返回值
     */

    public int getIndex(Signature signature) {
        if(s0.equals(signature)) {
            return 0;
        } else if(s1.equals(signature)) {
            return 1;
        } else if(s2.equals(signature)) {
            return 2;
        }
        return -1; //表示没找到
    }

    // 在我们Proxy中调用invoke方法时,内部就会调用到FastClass的invok方法
    // 根据 getIndex 返回的编号 正常调用 目标对象的方法
    public Object invoke(int index, Object proxy, Object[] args) {
        if(index == 0) {
            ((Proxy) proxy).saveSuper();
            return null;
        } else if(index == 1) {
            ((Proxy) proxy).saveSuper((int) args[0]);
            return null;
        } else if(index == 2) {
            ((Proxy) proxy).saveSuper((long) args[0]);
            return null;
        } else {
            throw new RuntimeException("无此方法");
        }
    }

    public static void main(String[] args) {
        ProxyFastClass fastClass = new ProxyFastClass();
        int index = fastClass.getIndex(new Signature("saveSuper", "(I)V"));
        System.out.println(index);
        fastClass.invoke(index, new Proxy(), new Object[]{1});

    }

}

收获

  1. 当调用 MethodProxy 的 invoke 或 invokeSuper 方法时, 会动态生成两个类
    • ProxyFastClass 配合代理对象一起使用, 避免反射
    • TargetFastClass 配合目标对象一起使用, 避免反射 (Spring 用的这种)
  2. TargetFastClass 记录了 Target 中方法与编号的对应关系
    • save(long) 编号 2
    • save(int) 编号 1
    • save() 编号 0
    • 首先根据方法名和参数个数、类型, 用 switch 和 if 找到这些方法编号
    • 然后再根据编号去调用目标方法, 又用了一大堆 switch 和 if, 但避免了反射
  3. ProxyFastClass 记录了 Proxy 中方法与编号的对应关系,不过 Proxy 额外提供了下面几个方法
    • saveSuper(long) 编号 2,不增强,仅是调用 super.save(long)
    • saveSuper(int) 编号 1,不增强, 仅是调用 super.save(int)
    • saveSuper() 编号 0,不增强, 仅是调用 super.save()
    • 查找方式与 TargetFastClass 类似
  4. 为什么有这么麻烦的一套东西呢?
    • 避免反射, 提高性能, 代价是一个代理类配两个 FastClass 类, 代理类中还得增加仅调用 super 的一堆方法
    • 用编号处理方法对应关系比较省内存, 另外, 最初获得方法顺序是不确定的, 这个过程没法固定死
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Aholic 冲冲冲

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值