【面试精讲】Java动态代理是如何实现的?JDK Proxy 和 CGLib 有什么区别?

Java动态代理是如何实现的?JDK Proxy 和 CGLib 有什么区别?

目录


前言

本文深入探讨了Java动态代理的实现机制,分别介绍了使用JDK Proxy和CGLib两种不同方式来实现动态代理。

文章进一步对比了JDK Proxy与CGLib的主要区别,JDK Proxy主要依赖于java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口,它允许在运行时动态创建实现了一组接口的代理类实例,但仅限于代理那些已实现接口的类。相比之下,CGLib通过在运行时生成目标类的子类来实现代理,从而克服了JDK Proxy只能代理接口的限制。包括代理对象的类型、性能差异、使用场景以及依赖问题。尽管两者都可以实现动态代理,但选择哪一种方式取决于具体需求:如果目标对象已经实现了接口,则JDK Proxy是一个简单直接的选择;若需要代理没有实现接口的普通类,则CGLib是更合适的选择。

文中还探讨了Spring框架中动态代理的应用,Spring可以根据情况自动选择使用JDK Proxy或CGLib来实现代理,为开发者提供了更高层次的抽象和便利。同时,也简要介绍了Lombok库是如何利用代理原理来减少样板代码并提高开发效率的。

一、Java动态代理的实现

Java动态代理主要依赖于java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口来实现。其基本思想是,在运行时动态创建一个实现了一组接口的代理类的实例,然后将对该实例的所有方法调用转发给一个处理器(即实现了InvocationHandler接口的类的实例)。

Java动态代理是Java高级编程中的一项强大功能,它允许开发者在运行时创建代理实例来控制对其他对象的访问。这种技术广泛应用于AOP(面向切面编程)、RPC(远程过程调用)框架、事务管理等领域。本文旨在深入探讨Java动态代理的实现机制,并比较JDK Proxy和CGLib两种实现方式的异同。

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

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

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

1、使用JDK Proxy实现动态代理

JDK动态代理只能代理实现了接口的类。

  • 定义接口:首先定义一个或多个接口,以及它们的实现类。
  • 创建InvocationHandler:实现InvocationHandler接口,重写invoke方法。在这里可以插入自定义的逻辑,比如日志记录、权限检查等。
  • 生成代理对象:通过调用Proxy.newProxyInstance方法,传入类加载器、接口数组以及InvocationHandler实例,来动态生成代理对象。
public interface Subject {
    void doSomething();
}

public class RealSubject implements Subject {
    @Override
    public void doSomething() {
        System.out.println("Doing something...");
    }
}

public class MyInvocationHandler implements InvocationHandler {
    private Object target;
    
    public MyInvocationHandler(Object target) {
        this.target = target;
    }
    
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // Before advice
        System.out.println("Before method call");
        Object result = method.invoke(target, args);
        // After advice
        System.out.println("After method call");
        return result;
    }
}

Subject subject = (Subject) Proxy.newProxyInstance(
    RealSubject.class.getClassLoader(),
    new Class[]{Subject.class},
    new MyInvocationHandler(new RealSubject())
);

subject.doSomething();

2、使用CGLib实现动态代理

与JDK Proxy不同,CGLib能够代理未实现接口的类。它通过在运行时动态生成被代理类的子类,来拦截对父类方法的调用。

  • 定义类:直接定义一个类,而非接口及其实现。
  • 创建MethodInterceptor:实现MethodInterceptor接口,重写intercept方法,在该方法中添加自定义逻辑。
  • 生成代理对象:通过使用CGLib提供的Enhancer类,设置父类和回调,从而创建代理对象。
public class RealSubject {
    public void doSomething() {
        System.out.println("Doing something...");
    }
}

Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(RealSubject.class);
enhancer.setCallback(new MethodInterceptor() {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        // Before advice
        System.out.println("Before method call");
        Object result = proxy.invokeSuper(obj, args);
        // After advice
        System.out.println("After method call");
        return result;
    }
});


RealSubject subject = (RealSubject) enhancer.create();
subject.doSomething();

二、JDK Proxy 与 CGLib 的区别

尽管JDK Proxy和CGLib都可用于实现Java动态代理,但它们之间存在一些关键差异:

  1. 代理对象类型:JDK Proxy只能代理实现了接口的类;而CGLib可以直接代理普通类。
  2. 性能:CGLib在运行时生成代理类的子类,通常认为其性能略优于JDK Proxy。但在大多数场景下,这种性能差异不大。
  3. 使用场景:如果目标对象已经实现了接口,使用JDK Proxy是一个简单直接的选择。如果需要代理没有实现接口的类,则必须使用CGLib。
  4. 依赖:JDK Proxy无需额外依赖,因为它是Java核心库的一部分;而CGLib需要添加CGLib库作为项目依赖。
  5. JDK Proxy 是 Java 语言自带的功能,无需通过加载第三方类实现;
  6. Java 对 JDK Proxy 提供了稳定的支持,并且会持续的升级和更新 JDK Proxy,例如 Java 8 版本中的 JDK Proxy 性能相比于之前版本提升了很多;
  7. JDK Proxy 是通过拦截器加反射的方式实现的;
  8. JDK Proxy 只能代理继承接口的类;
  9. JDK Proxy 实现和调用起来比较简单;
  10. CGLib 是第三方提供的工具,基于 ASM 实现的,性能比较高;
  11. CGLib 无需通过接口来实现,它是通过实现子类的方式来完成调用的。

三、Spring中的动态代理

动态代理的常见使用场景有 RPC 框架的封装、AOP(面向切面编程)的实现、JDBC 的连接等。

Spring 框架中同时使用了两种动态代理 JDK Proxy 和 CGLib,当 Bean 实现了接口时,Spring 就会使用 JDK Proxy,在没有实现接口时就会使用 CGLib,我们也可以在配置中指定强制使用 CGLib,只需要在 Spring 配置中添加  <aop:aspectj-autoproxy proxy-target-class=”true”/>  即可。

四、 Lombok代理原理

Lombok是一个工具类可以解决又重复的代码,如 Setter、Getter、toString、equals 和 hashCode 等等,向这种方法都可以使用 Lombok 注解来完成。 需要在 IDE 中安装 Lombok 插件,如下图所示:

Lombok 的实现和反射没有任何关系,Lombok 是在编译期就为我们生成了对应的字节码其实 Lombok 是基于 Java 1.6 实现的 JSR 269: Pluggable Annotation Processing API 来实现的,也就是通过编译期自定义注解处理器来实现的,它的执行步骤如下:

 从流程图中可以看出,在编译期阶段,当 Java 源码被抽象成语法树(AST)之后,Lombok 会根据自己的注解处理器动态修改 AST,增加新的代码(节点),在这一切执行之后就生成了最终的字节码(.class)文件,这就是 Lombok 的执行原理。

总结

Java动态代理是Java高级编程中的一个强大工具,它提供了一种灵活的方式来增强方法调用。通过对比JDK Proxy和CGLib,可以看出每种方法各有优势和适用场景。选择合适的动态代理实现方式,可以帮助开发者编写更加灵活和高效的代码。在实际开发中,根据具体需求选择最适合的代理方式,是提升项目质量和维护性的关键。

如果本文对你有帮助 欢迎 关注 、点赞 、收藏 、评论, 博主才有动力持续记录遇到的问题!!!

博主v:XiaoMing_Java

 📫作者简介:嗨,大家好,我是  小明(小明Java问道之路)互联网大厂后端研发专家,2022博客之星TOP3 / 博客专家 / CSDN后端内容合伙人、InfoQ(极客时间)签约作者、阿里云签约博主、全网 6 万粉丝博主。

🍅 文末获取联系 🍅  👇🏻 精彩专栏推荐订阅收藏 👇🏻

专栏系列(点击解锁)

学习路线(点击解锁)

知识定位

🔥Redis从入门到精通与实战🔥

Redis从入门到精通与实战

围绕原理源码讲解Redis面试知识点与实战

🔥MySQL从入门到精通🔥

MySQL从入门到精通

全面讲解MySQL知识与企业级MySQL实战

🔥计算机底层原理🔥

深入理解计算机系统CSAPP

以深入理解计算机系统为基石,构件计算机体系和计算机思维

Linux内核源码解析

围绕Linux内核讲解计算机底层原理与并发

🔥数据结构与企业题库精讲🔥

数据结构与企业题库精讲

结合工作经验深入浅出,适合各层次,笔试面试算法题精讲

🔥互联网架构分析与实战🔥

企业系统架构分析实践与落地

行业最前沿视角,专注于技术架构升级路线、架构实践

互联网企业防资损实践

互联网金融公司的防资损方法论、代码与实践

🔥Java全栈白宝书🔥

精通Java8与函数式编程

本专栏以实战为基础,逐步深入Java8以及未来的编程模式

深入理解JVM

详细介绍内存区域、字节码、方法底层,类加载和GC等知识

深入理解高并发编程

深入Liunx内核、汇编、C++全方位理解并发编程

Spring源码分析

Spring核心七IOC/AOP等源码分析

MyBatis源码分析

MyBatis核心源码分析

Java核心技术

只讲Java核心技术

版权声明:本文为博主作者:小 明原创文章,版权归属原作者,如果侵权,请联系我们删除!

原文链接:https://blog.csdn.net/FMC_WBL/article/details/136580693

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
乘风的头像乘风管理团队
上一篇 2024年4月1日
下一篇 2024年4月1日

相关推荐