JDK Proxy 和 CGLib 有什么区别?

1,675 阅读4分钟

这是我参与2022首次更文挑战的第10天,活动详情查看:2022首次更文挑战

JDK Proxy 和 CGLib 有什么区别?

通常情况,大部分人在工作中都或多或少的使用过动态代理,无论是在Java基础使用还是Spring框架的使用,动态代理是程序在运行期间动态构建代理对象和动态调用代理方法的一种机制。动态代理主要又有两种实现机制,一种是JDK基于反射的动态代理,另外一种是CGLib基于ASM机制实现的动态代理。下面来讲一下两种实现的区别和如何进行实现

JDK Proxy 和 CGLib 的区别

  • JDK动态代理是使用反射机制来实现的,而CGLib则是基于ASM机制来实现,两者实现机制不一样,性能不一样

  • JDK动态代理是Java语言自带的功能,并且会持续的更新和升级JDK动态代理无需考虑维护问题,而CGLib是基于第三方提供的工具来进行实现,两者的实现常见不一样。

  • JDK动态代代理必须通过接口来进行实现,并且调用也相对比较简单,而CGLib并不需要通过接口进行实现,调用相对复杂。

JDKProxy动态代理实现

JDK动态代理是通过反射类Proxy和InvocationHandler接口实现的,由于JDK中所有的动态代理类都必须要实现一个接口,也就是说动态代理类必须要实现动态代理接口中定义的方法进行代理,这就导致使用的反射效率不高。

  • 代码实现
public class JdkTokProxy<T> implements InvocationHandler {

    private T target;

    public JdkTikTokProxy(T target) {
        this.target = target;
    }

    public static <T> T getProxy(T t) {
        Object o = Proxy.newProxyInstance(t.getClass().getClassLoader()
                , t.getClass().getInterfaces()
                , new JdkTikTokProxy(t));
        return (T) o;
    }
    
    @Override
    public Object invoke(Object proxy,
                         Method method,
                         Object[] args) throws Throwable {
        //反射执行
        System.out.println("真正执行被代理对象的方法");
        Object invoke = method.invoke(target, args);
        System.out.println("返回值:" + invoke);
        return null;
    }
}

通过上面代码可以知道,JDK动态代理是通过使用Proxy类中的newProxyInstance方法创建出动态代理对象。

而newProxyInstance方法的参数主要是

参数描述
ClassLoader loader当前被代理对象的类加载器, 他是用于加载代理对象字节码的。和被代理对象使用相同的类加载器。固定写法
Class<?>[] interfaces当前被代理对象所实现的所有接口字节码数组,它是用于让代理对象和被代理对象有相同方法。固定写法
InvocationHandler h当前被代理对象执行目标方法的时候,我们使用h可以定义拦截增强方法,他是让我们写如何代理。我们一般写一个该接口的实现类,通常情况加都是匿名内部类,但不是必须的。

invoke方法的主要作用是执行被代理对象的任何接口方法都会经过该方法,或者说是一个监听方法,invoke主要的方法参数

参数描述
proxy代理对象的引用
method当前执行的方法
args当前执行方法所需的参数
return和被代理对象有相同返回值

JDK Proxy 实现动态代理的核心是实现 Invocation 接口,我们查看 Invocation 的源码,会发现里面其实只有一个 invoke() 方法,

public interface InvocationHandler { 
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable; 
}

这是因为在动态代理中有一个重要的角色也就是代理器,它用于统一管理被代理的对象,显然 InvocationHandler 就是这个代理器,而 invoke() 方法则是触发代理的执行方法,我们通过实现 Invocation 接口来拥有动态代理的能力。

CGLib的实现

如果使用的是CGLib实现的动态代理,那么就不需要代理类来实现接口,从而使性能增加,因为CGLib底层使用的是ASM框架,而ASM框架则通过字节码技术来生成代理类。

动态生成一个要代理的子类,子类重写要代理的类的所有不是final的方法。在子类中采用方法拦截技术拦截所有的父类方法的调用,顺势织入横切逻辑,它比Java反射的jdk动态代理要快

Cglib是一个强大的、高性能的代码生成包,它被广泛应用在许多AOP框架中,为他们提供方法的拦截

  • 代码实现如下
public class CglibProxy {
    public static <T> T createProxy(T t) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(t.getClass());
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object obj,
                                    Method method,
                                    Object[] args,
                                    MethodProxy proxy) throws Throwable {
   
                System.out.println("cglib开始了");
                Object invoke = proxy.invokeSuper(obj, args);
                return invoke;
            }
        });
        Object o = enhancer.create();
        return (T) o;
    }
}

从上面的代码可以得知,CGLib是基于子类的实现的动态代理,并且通过使用Enhancer类中的create方法来创建代理对象。因此被代理类不能被关键字 final 修饰,如果被 final 修饰,再使用 Enhancer 设置父类时会报错,动态代理的构建会失败。