Cglib动态代理

2020-09-17  本文已影响0人  因你而在_caiyq

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

从一个案列引入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()方法呢?

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调用流程

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执行效率更高。

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

上一篇下一篇

猜你喜欢

热点阅读