JDK Proxy 和 CGLib 有什么区别?

JDK Proxy 和 CGLib 有什么区别?

文章目录

  • JDK Proxy 和 CGLib 有什么区别?
    • 前言
    • 项目环境
    • 1.主要区别
    • 2.JDK Proxy 动态代理
    • 3.CGLib 的实现
    • 4.Lombok
    • 5.总结
    • 6.参考

前言

JDK 动态代理的实现方式是反射。

反射机制 是指程序在运行期间可以访问、检测和修改其本身状态或行为的一种能力,使用反射我们可以调用任意一个类对象,以及类对象中包含的属性及方法。

CGLib 实现动态代理是基于 ASM(一个 Java 字节码操作框架)而非反射实现的。

动态代理是一种行为方式,反射或 ASM 只是它的一种实现手段而已。

项目环境

  • Java 8
  • Spring framework 5.2.2.RELEASE
  • github 地址:https://github.com/huajiexiewenfeng/spring-aop-demos

1.主要区别

JDK Proxy 和 CGLib 的区别主要体现在以下几个方面:

  • JDK Proxy 是 Java 语言自带的功能,无需通过加载第三方类实现;
  • Java 对 JDK Proxy 提供了稳定的支持,并且会持续的升级和更新 JDK Proxy;
  • JDK Proxy 是通过拦截器加反射的方式实现的;
  • JDK Proxy 只能代理继承接口的类;
  • JDK Proxy 实现和调用起来比较简单;
  • CGLib 是第三方提供的工具,基于 ASM 实现的,性能比较高;
  • CGLib 无需通过接口来实现,它是通过实现子类的方式来完成调用的。

2.JDK Proxy 动态代理

JDK Proxy 动态代理的实现无需引用第三方类,只需要实现 InvocationHandler 接口,重写 invoke() 方法即可,示例代码如下:

public class DynamicProxyDemo {

    public static void main(String[] args) {
        Subject subject = new RealSubject();
        Subject proxyInstance = (Subject) java.lang.reflect.Proxy.newProxyInstance(
                subject.getClass().getClassLoader(),
                new Class<?>[]{Subject.class},
                new InvocationHandlerImpl(subject));
        proxyInstance.request();
    }

    static class InvocationHandlerImpl implements InvocationHandler {

        private Object target;

        public InvocationHandlerImpl(Object target) {
            this.target = target;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            Object reuslt = null;
            postOperation();
            reuslt = method.invoke(target, args);
            preOperation();
            return reuslt;
        }

        private void postOperation() {
            System.out.println("后置处理...");
        }

        private void preOperation() {
            System.out.println("前置处理...");
        }
    }

}

执行结果:

后置处理...
request 方法执行
前置处理...

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

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

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

3.CGLib 的实现

示例代码:

public class CGLibDemo {

    static class Car {
        public void run() {
            System.out.println("the car is running");
        }
    }

    static class CglibProxy<T> implements MethodInterceptor {

        private T target;// 代理对象

        public T getInstance(T target) {
            this.target = target;
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(this.target.getClass());
            enhancer.setCallback(this);
            return (T) (enhancer.create());
        }

        @Override
        public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
            preOperation();
            Object retVal = methodProxy.invoke(this.target, args);
            postOperation();
            return retVal;
        }

        private void postOperation() {
            System.out.println("后置处理...");
        }

        private void preOperation() {
            System.out.println("前置处理...");
        }

    }

    public static void main(String[] args) {
        CglibProxy<Car> cglibProxy = new CglibProxy<>();
        Car car = cglibProxy.getInstance(new Car());
        car.run();
    }

}

执行结果:

前置处理...
the car is running
后置处理...

可以看出 CGLib 和 JDK Proxy 的实现代码比较类似,都是通过实现代理器的接口,再调用某一个方法完成动态代理的,唯一不同的是,CGLib 在初始化被代理类时,是通过 Enhancer 对象把代理对象设置为被代理类的子类来实现动态代理的。因此被代理类不能被关键字 final 修饰,如果被 final 修饰,再使用 Enhancer 设置父类时会报错,动态代理的构建会失败。

4.Lombok

Lombok 属于 Java 的一个热门代码生成工具类,使用它可以自动生成 Setter、Getter、toString、equals 和 hashCode 等等方法。

看一个实际的示例如下:
JDK Proxy 和 CGLib 有什么区别?_第1张图片
反编译的 class 文件如下:
JDK Proxy 和 CGLib 有什么区别?_第2张图片
可以看出 Lombok 是在编译期就为我们生成了对应的字节码。

Lombok 是基于 Java 1.6 实现的 JSR 269: Pluggable Annotation Processing API 来实现的,也就是通过编译期自定义注解处理器来实现的,它的执行步骤如下:
JDK Proxy 和 CGLib 有什么区别?_第3张图片
从流程图中可以看出,在编译期阶段,当 Java 源码被抽象成语法树(AST)之后,Lombok 会根据自己的注解处理器动态修改 AST,增加新的代码(节点),在这一切执行之后就生成了最终的字节码(.class)文件,这就是 Lombok 的执行原理。

5.总结

本章介绍了 JDK Proxy 和 CGLib 的区别

  • JDK Proxy 是 Java 语言内置的动态代理,必须要通过实现接口的方式来代理相关的类;
  • CGLib 是第三方提供的基于 ASM 的高效动态代理类,它通过实现被代理类的子类来实现动态代理的功能,因此被代理的类不能使用 final 修饰。

6.参考

  • 《Java 源码剖析 34 讲》- 王磊

你可能感兴趣的:(JDK Proxy 和 CGLib 有什么区别?)