Cglib动态代理

Cglib动态代理

profile-avatar

又双叒叕是程序汪2021-01-08 23:02

cglib动态代理:


cglib的优点:

  1. 基于字节码,生成真实对象的子类。
  2. 运行效率高于JDK代理
  3. 不需要实现接口

cglib的缺点:

  1. 非JDK功能,需要额外导入java包

使用SpringAOP时,只要出现Proxy和真实对象转换异常

  1. 设置ture使用cglib
  2. 设置false使用JDK(默认值)
<aop:aspectj-autoproxy
  proxy-target-class=''true''></aop:asepectj-autoproxy>

CGLib动态代理

  这里我们先简单说一下这两种代理方式最大的区别,JDK动态代理是基于接口的方式,换句话来说就是代理类和目标类都实现同一个接口,那么代理类和目标类的方法名就一样了,这种方式上一篇说过了;CGLib动态代理是代理类去继承目标类,然后重写其中目标类的方法啊,这样也可以保证代理类拥有目标类的同名方法;

  看一下CGLib的基本结构,下图所示,代理类去继承目标类,每次调用代理类的方法都会被方法拦截器拦截,在拦截器中才是调用目标类的该方法的逻辑,结构还是一目了然的;

 

1.CGLib的基本使用

  目标类(一个公开方法,另外一个用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,这个用起来不怎么直接。。。。

  我简单说说用法: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规范!!!)

 

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$方法一$这种方法,也就是调用对应的目标类的方法一;

  一般我们要添加自己的逻辑就是在方法拦截器那里。。。。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值