美文网首页
Cglib动态代理

Cglib动态代理

作者: 因你而在_caiyq | 来源:发表于2020-09-17 22:01 被阅读0次

    原创文章,转载请注明原文章地址,谢谢!

    • 最底层的是字节码ByteCode,字节码是Java为了保证“一次编译,到处运行”而产生的一种虚拟的指令格式。
    • 位于字节码之上的是ASM,这是一种直接操作字节码的框架,应用ASM需要对Java字节码、Class结构比较熟悉。
    • 位于ASM之上的是CGLIB、Groovy、BeanShell,后两种并不是Java体系中的内容而是脚本语言,它们通过ASM框架生成字节码变相执行Java代码,这说明在JVM中执行程序并不一定非要写Java代码,只要你能生成Java字节码,JVM并不关心字节码的来源,当然通过Java代码生成的JVM字节码是通过编译器直接生成的,算是最“正统”的JVM字节码。
    • 位于CGLIB、Groovy、BeanShell之上的就是Hibernate、Spring AOP这些框架了。
    • 最上层的是Applications,即具体应用,一般都是一个Web项目或者本地跑一个程序。
    从一个案列引入Cglib

    真实类

    public class HelloService {
        public HelloService() {
            System.out.println("HelloService Constructor...");
        }
    
        /**
         * 该方法不能被子类覆盖,Cglib是无法代理final修饰的方法的
         */
        final public String sayOthers(String name) {
            System.out.println("HelloService...sayOthers..." + name);
            return null;
        }
    
        public void sayHello() {
            System.out.println("HelloService...sayHello...");
        }
    }
    

    cglib方法拦截器类

    public class MyMethodInterceptor implements MethodInterceptor {
        /**
         * cglib
         *
         * @param o cglib生成的代理对象
         * @param method 被代理对象的方法
         * @param objects 方法入参
         * @param methodProxy 代理方法
         * @return
         * @throws Throwable
         */
        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            System.out.println("before advice...");
            Object object = methodProxy.invokeSuper(o, objects);
            System.out.println("after advice...");
            return object;
        }
    }
    

    测试类

    public class Main {
        public static void main(String[] args) {
            //代理类class文件存入本地磁盘方便我们反编译查看源码
            System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "F:\\workspace\\demo");
            //通过CGLIB动态代理获取代理对象的过程
            Enhancer enhancer = new Enhancer();
            //设置enhancer对象的父类
            enhancer.setSuperclass(HelloService.class);
            //设置enhancer的回调对象
            enhancer.setCallback(new MyMethodInterceptor());
            //创建代理对象
            HelloService proxy = (HelloService) enhancer.create();
            //通过代理对象调用目标方法
            proxy.sayHello();
        }
    }
    

    测试结果

    HelloService Constructor...
    before advice...
    HelloService...sayHello...
    after advice...
    

    从案例代码可以了解到,如果你了解过jdk的动态代理,那么与之比较,jdk动态代理需要实现代理类的接口,编写调用处理器,且要实现InvocationHandler。而cglib采用的是继承的方式,最终生成的代理类,是继承了真实类。编写自定义的方法拦截器,都要实现MethodInterceptor,这是使用cglib代理必须要实现的接口,然后通过其intercept方法实现具体的逻辑。而代理类是怎样和真实类产生联系的呢?看上述的测试代码,通过Enhancer对象创建代理类,并设置了其父类就是真实类,这样生成的代理类就和真实类有了继承关系。那么这个Enhancer是什么呢?它是一个字节码增强器,用来为无接口的类创建代理的,这里先不过多介绍。

    在上述运行测试的过程中,因为在其上面设置类保存字节码到磁盘,所以在运行结束后,磁盘怒路下多了几个class文件,这些便是在运行过程中生成的相关的代理类。不同于jdk的生成的代理类,这里cglib生成很多代理类。这其实是因为cglib在运行的过程中,为真实类,以及这个过程中用到的FastClass都生成了代理类。这些具体是什么东东,我们后面再说。

    一个小小的案例,让我们知道了如何使用cglib,以及最后的结果。但是如果你是一个愿意刨根问底的,那么一定会有一些疑问。cglib到底是怎样完成这样的代理的?是如果实现方法拦截的?所以接下来呢,我们还是要带着问题,一起深入了解一下。

    Cglib原理分析

    我们先从生成的字节码入手,看看cglib如何利用它们来完成代理的。HelloService$$EnhancerByCGLIB$$750c1a60.class。前面说了,cglib是采用继承的方式,所以结果也不难看出,生成的字节码类继承了真实类,这样是可以重写其父类的方法,并且还可以添加一些额外的逻辑。

    public class HelloService$$EnhancerByCGLIB$$750c1a60 extends HelloService implements Factory {
        private boolean CGLIB$BOUND;
        public static Object CGLIB$FACTORY_DATA;
        private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
        private static final Callback[] CGLIB$STATIC_CALLBACKS;
        private MethodInterceptor CGLIB$CALLBACK_0;
        private static Object CGLIB$CALLBACK_FILTER;
        //真实对象的方法
        private static final Method CGLIB$sayHello$0$Method;
        //代理对象的代理方法(都与真实对象的方法一一对应)
        private static final MethodProxy CGLIB$sayHello$0$Proxy;
        private static final Object[] CGLIB$emptyArgs;
        private static final Method CGLIB$equals$1$Method;
        private static final MethodProxy CGLIB$equals$1$Proxy;
        private static final Method CGLIB$toString$2$Method;
        private static final MethodProxy CGLIB$toString$2$Proxy;
        private static final Method CGLIB$hashCode$3$Method;
        private static final MethodProxy CGLIB$hashCode$3$Proxy;
        private static final Method CGLIB$clone$4$Method;
        private static final MethodProxy CGLIB$clone$4$Proxy;
    
        //静态代码块,会优先加载
        static void CGLIB$STATICHOOK1() {
            CGLIB$THREAD_CALLBACKS = new ThreadLocal();
            CGLIB$emptyArgs = new Object[0];
            //利用Class.forName创建对象,后面的全限定名就是代理对象
            Class var0 = Class.forName("com.alibaba.proxy.cglib.HelloService$$EnhancerByCGLIB$$750c1a60");
            //这里的var1会在下面进行创建,这里只是声明
            Class var1;
            Method[] var10000 = ReflectUtils.findMethods(new String[]{"equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods());
            CGLIB$equals$1$Method = var10000[0];
            CGLIB$equals$1$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$1");
            CGLIB$toString$2$Method = var10000[1];
            CGLIB$toString$2$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$2");
            CGLIB$hashCode$3$Method = var10000[2];
            CGLIB$hashCode$3$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$3");
            CGLIB$clone$4$Method = var10000[3];
            CGLIB$clone$4$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$4");
            //上面的var1声明,这里进行创建对象并赋值。同样是通过Class.forName创建,后面是真实对象的全限定名
            CGLIB$sayHello$0$Method = ReflectUtils.findMethods(new String[]{"sayHello", "()V"}, (var1 = Class.forName("com.alibaba.proxy.cglib.HelloService")).getDeclaredMethods())[0];
            //创建一个MethodProxy对象,将代理类、真实类、代理方法、真实方法都作为入参
            CGLIB$sayHello$0$Proxy = MethodProxy.create(var1, var0, "()V", "sayHello", "CGLIB$sayHello$0");
        }
    

    CGLIB$sayHello$0()方法,是代理方法,methodProxy.invokeSuper会调用。下面的sayHello是真实方法,methodProxy.invoke会调用。这里解释了为什么在拦截器中调用methodProxy.invoke会死循环,因为下面的代码中一直在调用intercept。

    final void CGLIB$sayHello$0() {
        super.sayHello();
    }
    
    public final void sayHello() {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }
    
        if (var10000 != null) {
            //调用intercept方法,也就是在自定义的Intercepter类中实现的方法。
            var10000.intercept(this, CGLIB$sayHello$0$Method, CGLIB$emptyArgs, CGLIB$sayHello$0$Proxy);
        } else {
            super.sayHello();
        }
    }
    

    一直到上面,简单说一下流程:代理对象调用sayHello,然后调用拦截器intercept,然后执行拦截器中的前面代码,然后执行invokeSuper,会执行CGLIB$sayHello$0方法,然后调用父类sayHello,然后执行拦截器后面代码。

    那么从invokeSuper方法中为什么就能调用到CGLIB$sayHello$0()方法呢?

    • MethodProxy.create:在代理类中create一个MethodProxy,将被代理对象与方法传入Signature1,将代理对象与方法传入Signature2,将代理类对象与被代理类对象传入MethodProxy.CreateInfo,Signature1与MethodProxy.CreateInfo可以认为是实体类存储信息的。
    • 调用methodProxy.invokeSuper了,调用invokeSuper时又会首先调用init方法。
    • init方法就是给在create方法中没有赋值的fastclassInfo赋值,将createInfo与Signature1与Signature2中的信息取出,转成fastclass,如果fastclass在缓存中有就从缓存中取,没有的话就生成新的fastclass。
    • 通过Signature1与Signature2获得方法的索引,存入i1与i2。
    • 通过fci.f2.invoke(fci.i2, obj, args),fastclass反射调用代理对象的代理方法的索引,直接定位到代理方法。
    • 代理方法中调用super.sayHello()
    public class MethodProxy {
    
        private Signature sig1;
    
        private Signature sig2;
    
        private CreateInfo createInfo;
    
        private final Object initLock = new Object();
    
        private volatile FastClassInfo fastClassInfo;
    
        /**
         * For internal use by {@link Enhancer} only; see the {@link org.springframework.cglib.reflect.FastMethod} class
         * for similar functionality.
         */
        public static MethodProxy create(Class c1, Class c2, String desc, String name1, String name2) {
            MethodProxy proxy = new MethodProxy();
            proxy.sig1 = new Signature(name1, desc);
            proxy.sig2 = new Signature(name2, desc);
            proxy.createInfo = new CreateInfo(c1, c2);
            return proxy;
        }
    
    public Object invokeSuper(Object obj, Object[] args) throws Throwable {
        try {
            init();
            FastClassInfo fci = fastClassInfo;
            //fastCLass.invoke(代理方法索引,代理对象,方法参数)
            return fci.f2.invoke(fci.i2, obj, args);
        }
        catch (InvocationTargetException e) {
            throw e.getTargetException();
        }
    }
    

    MethodProxy调用流程

    • 首先main方法中调用getInstance调用Enhancer创建代理类class文件,并将代理类引用返回。
    • 然后调用代理对象的sayHello()
    • 然后通过反编译代理类,查看sayHello方法,发现在sayHello中会调用拦截器方法。
    • 然后在拦截器方法中会通过MethodProxy调用invokeSuper方法。
    FastClass机制

    Cglib动态代理执行代理方法效率之所以比JDK高是因为Cglib采用了FastClass机制,它为代理类和被代理类各生成了一个class,这个class会为代理类与被代理类的方法分类index。这个index作为方法参数,FastClass可以直接定位到要调用的方法进行调用,这样省去了反射调用,所以效率比JDK动态代理快。FastClass不是与代理类一起生成的,而是在第一次执行MethodProxy invoke/invokeSuper时生成的并放入缓存。

    public class HelloService$$FastClassByCGLIB$$8656ab6f extends FastClass {
        public HelloService$$FastClassByCGLIB$$8656ab6f(Class var1) {
            super(var1);
        }
    
        public int getIndex(Signature var1) {
            String var10000 = var1.toString();
            switch(var10000.hashCode()) {
            case -1488716497:
                if (var10000.equals("sayOthers(Ljava/lang/String;)Ljava/lang/String;")) {
                    return 1;
                }
                break;
            }
    
            return -1;
        }
    
        public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
            HelloService var10000 = (HelloService)var2;
            int var10001 = var1;
    
            try {
                switch(var10001) {
                case 0:
                    var10000.sayHello();
                    return null;
                case 1:
                    return var10000.sayOthers((String)var3[0]);
                case 2:
                    return new Boolean(var10000.equals(var3[0]));
                case 3:
                    return var10000.toString();
                case 4:
                    return new Integer(var10000.hashCode());
                }
            } catch (Throwable var4) {
                throw new InvocationTargetException(var4);
            }
        }
    }
    

    因为生成的FastClass代理类较长,这里只截取其中一部分,只为了说明问题。这里面包含了getIndex方法和invoke方法。getIndex方法会为代理类和真实类的方法分类index,这样每个index会对应到具体方法,那么在上面那个invokeSuper那里,实际上就是用fastClass对象调用的invoke方法,传入的参数就包含这个index,那么在invoke方法中,就直接根据index定位到具体方法执行了。

    jdk动态代理和cglib动态代理的区别

    1、jdk动态代理是实现了被代理对象的接口,cglib是继承了被代理对象。
    2、jdk和cglib都是在运行期生成字节码,jdk是直接写class字节码,cglib使用ASM框架写class字节码,cglib代理实现更复杂,生成代理类比jdk效率低。
    3、jdk调用代理方法,是通过反射机制调用,cglib是通过FastClass机制直接调用方法,cglib执行效率更高。

    博客内容仅供自已学习以及学习过程的记录,如有侵权,请联系我删除,谢谢!

    相关文章

      网友评论

          本文标题:Cglib动态代理

          本文链接:https://www.haomeiwen.com/subject/wmuryktx.html