又被问了,JDK 动态代理与 CGLIB 的区别?

大家好,我是鸭鸭!

此答案节选自鸭鸭最近弄的 面试鸭小程序,更多 大厂常问面试题,可以点击下面的小程序进行阅读哈!

回答:

JDK 动态代理是基于接口的,所以要求代理类一定是有定义接口的。

CGLIB 基于 ASM 字节码生成工具,它是通过继承的方式来实现代理类,所以要注意 final 方法。

它们之间的性能随着 JDK 版本的不同而不同,以下内容取自:haiq的博客

  • jdk6 下,在运行次数较少的情况下,jdk动态代理与 cglib 差距不明显,甚至更快一些;而当调用次数增加之后, cglib 表现稍微更快一些
  • jdk7 下,情况发生了逆转!在运行次数较少(1,000,000)的情况下,jdk动态代理比 cglib 快了差不多30%;而当调用次数增加之后(50,000,000), 动态代理比 cglib 快了接近1倍
  • jdk8 表现和 jdk7 基本一致

扩展 JDK 动态代理

JDK 动态代理是基于接口的代理,因此要求代理类一定是有定义的接口,使用 java.lang.reflect.Proxy 类和 java.lang.reflect.InvocationHandler 接口实现。

以下为一个简单 JDK 动态代理示例:

// 接口
public interface Service {
    void perform();
}

// 需要被代理的实现类
public class ServiceImpl implements Service {
    @Override
    public void perform() {
        System.out.println("mianshiya.com");
    }
}

JDK 动态代理处理类:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class ServiceInvocationHandler implements InvocationHandler {
    private final Object target;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Before method invoke");
        Object result = method.invoke(target, args);
        System.out.println("After method invoke");
        return result;
    }
}

创建并使用动态代理对象:

import java.lang.reflect.Proxy;

public class DynamicProxyDemo {
    public static void main(String[] args) {
        Service target = new ServiceImpl();
        Service proxy = (Service) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new ServiceInvocationHandler(target)
        );

        proxy.perform();
    }
}

我们再看看 JDK 动态代理实现原理:

  • 首先通过实现 InvocationHandler 接口得到一个切面类。
  • 然后利用 Proxy 根据目标类的类加载器、接口和切面类得到一个代理类。
  • 代理类的逻辑就是把所有接口方法的调用转发到切面类的 invoke() 方法上,然后根据反射调用目标类的方法。

再深一点点就是代理类会现在静态块中通过反射把所有方法都拿到存在静态变量中,我之前反编译看过代理类,我盲写了一下,大致长这样:

这一套下来 JDK 动态代理原理应该就很清晰了。

扩展 CGLIB

CGLIB 基于 ASM 字节码生成工具,它是通过继承的方式来实现代理类,所以不需要接口,可以代理普通类,但需要注意 final 方法(不可继承)。

同样来看个示例:

public class Service {
    public void perform() {
        System.out.println("mianshiya.com");
    }
}

CGLIB 动态代理处理类:

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class ServiceMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("Before method invoke");
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("After method invoke");
        return result;
    }
}

创建并使用动态代理对象:

import net.sf.cglib.proxy.Enhancer;

public class CglibDynamicProxyDemo {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Service.class);
        enhancer.setCallback(new ServiceMethodInterceptor());

        Service proxy = (Service) enhancer.create();
        proxy.perform();
    }
}

它是通过字节码生成技术而不是反射来实现调用的逻辑,具体就不再深入了。

最后

最后再推荐下鸭鸭目前努力在做面试小程序神器,已经有 3000 多道面试题目啦,欢迎大家来阅读!如果大家有不会的面试题,也可以在小程序内反馈!鸭鸭会第一时间为大家解答!

除了小程序版本,我们 web 端也上线啦:www.mianshiya.com

我是鸭鸭,我们下期见~

相关推荐

  • 编程语言座次图,谁才是老大?
  • ECCV 2024 | 提升GPT-4V、Gemini检测任务性能,你需要这种提示范式
  • 中科大联合华为诺亚提出Entropy Law,揭秘大模型性能、数据压缩率以及训练损失关系
  • 爆火免费书《深入理解深度学习》终于出中文版了
  • 盛名一时的BERT哪去了?这个问题的答案昭示了LLM范式的转变
  • 从空间智能到具身智能,跨维践行Sim2Real AI最高效路径
  • 挑战Scaling Law,Meta发布移动端350M小模型MobileLLM,性能比肩7B LLaMA-v2
  • 奥运史上AI首秀!谷歌Gemini将亮相巴黎,打造AI观赛新体验
  • 大模型时代结束?大佬齐预测:AI模型或需先缩小规模,才能再次扩大规模
  • 中国五连冠终结,美国重登IMO宝座!AI智商被第一题打回原形
  • 两次全球蓝屏,祸首竟是同一人?14年后,灭霸CEO再酿IT灾难
  • AB实验的采样分流技术演进以及Sutva假设与现实挑战
  • 如何看待微软这次蓝屏事件,中国为什么能风平浪静?
  • CrowdStrike灾难的7个紧急教训
  • 马斯克庆祝拜登退选;周鸿祎评微软蓝屏:中国安全软件立大功;字节豆包被曝“偷”用户数据练AI;传小红书职级将做扁平化调整
  • [Pnetlab实战演练]一步步教你通过SSH安全登陆防火墙!
  • 多目标跟踪相关开源数据集资源汇总
  • 【机器学习】基于scikit-learn进行特征工程
  • NUS提出ProcessPainter,从文本生成分步骤绘画过程
  • 科技部通报:多位国家杰青、长江学者,因请托、抄袭,被处理!