和我一起学 CGLIB 动态代理

311 阅读3分钟

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

上一篇文章 学习了 JDK 动态代理,JDK 动态代理有一个问题是只能代理实现了接口的类。为了解决这个问题,我们可以用 CGLIB 动态代理机制来避免。CGLIB 基于 ASM 的字节码生成库,可以在运行时对字节码进行修改和动态生成。

CGLIB 动态代理

MethodInterceptor 接口和 Enhancer 类

在 CGLIB 动态代理机制中核心是 MethodInterceptor 接口和 Enhancer 类。

我们需要自定义类实现 MethodInterceptor接口 并重写 intercept 方法,intercept 用于增强被代理类的方法。当代理类调用方法的时候,实际调用的是 MethodInterceptor 中的 intercept 方法。

public interface MethodInterceptor extends Callback {
    Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable;
}

  • Object object: object是 CGLIB 动态生成的代理类对象

  • Method method: Method 为实体类所调用的被代理的方法引用

  • Objectp[] args: 这个就是方法的参数列表

  • MethodProxy methodProxy : 这个就是生成的代理类对方法的引用。

通过 Enhancer类可以动态获取代理类,代理类是目标类的子类。Enhancer 类和 JDK 动态代理中的 Proxy 类类似,调用 Proxy 类的 newProxyInstance() 方法可以创建代理对象,同样,调用 Enhancercreate() 方法也可以创建代理对象,只是两者原理不同, Proxy 类通过反射创建代理对象,而 Enhancer 类通过修改字节码来实现,动态地创建给定类的子类。

使用小例子

首先创建需要被代理的类 RealSubjectrequest 方法是要被增强的目标方法。

public class RealSubject {
    @Override
    public void request() {
        System.out.println("real request");
    }
}

CGLIB 动态代理不需要实现与目标类一样的接口,而是通过方法拦截的方式实现代理,实现方法拦截接口 MethodInterceptorintercept 方法中我们在目标方法执行前和执行后都输出了信息。

public class MyMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("do sth before request");
        methodProxy.invokeSuper(object, args);
        System.out.println("do sth after request");
        return null;
    }
}

main 方法中,通过 Enhancer 类的 create() 方法获取到代理对象。

public class Main {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(RealSubject.class);
        enhancer.setCallback(new MyMethodInterceptor());

        RealSubject proxy = (RealSubject) enhancer.create();
        System.out.println("代理对象" + proxy.getClass());
        proxy.request();
    }
}

输出结果

代理对象class cglib.RealSubject$$EnhancerByCGLIB$$1248fed6
do sth before request
real request
do sth after request

JDK 动态代理和 CGLIB 动态代理的区别

  • 从使用上:JDK 动态代理只能代理实现了接口的类,CGLIB 动态代理生成的代理类是目标类的子类,目标类和方法不能加 final 修饰。

  • 从原理上:JDK 动态代理通过反射创建代理对象,CGLIB 利用 ASM 框架,对目标类生成的 class 文件加载进来,通过修改其字节码生成子类。

  • 从性能上:JDK6 前字节码技术生成代理类的性能比反射生成的效率要高,随着对 JDK 动态代理的优化,在 JDK8 的时候 JDK 动态代理的效率要高于 CGLIB 动态代理了。Spring 会在这两者之间切换,在 bean 有实现接口时,使用 JDK 动态代理,没有实现接口使用 CGLIB 动态代理。