当前位置: 首页 > article >正文

技术总结(十九)

一、静态代理和动态代理的对比

一、概念

  • 静态代理
    • 静态代理是在编译时期就已经确定好代理类和被代理类的关系。代理类和被代理类需要实现相同的接口或者继承相同的抽象类。代理类会持有被代理类的一个引用,并且在代理类的方法中调用被代理类的对应方法,同时可以在调用前后添加一些额外的逻辑。
    • 例如,假设有一个接口Subject,里面有一个方法doSomething()。被代理类RealSubject实现了Subject接口,代理类ProxySubject也实现了Subject接口。ProxySubject类中包含一个RealSubject的实例,在ProxySubjectdoSomething方法中,可以先进行一些日志记录或者权限检查等操作,然后调用RealSubjectdoSomething方法。
  • 动态代理
    • 动态代理是在运行时动态地生成代理类。它不需要像静态代理那样在编译时期就定义好代理类,而是通过 Java 的反射机制在运行时创建代理对象。动态代理类通常是实现了InvocationHandler接口,通过Proxy类的newProxyInstance方法来生成代理对象。
    • 比如,同样对于Subject接口和RealSubject实现类,在使用动态代理时,可以通过一个InvocationHandler的实现类来处理对RealSubject的方法调用,在这个实现类的invoke方法中,可以根据方法名等信息来决定是否添加额外的逻辑,以及如何调用真实的方法。

二、灵活性对比

  • 静态代理
    • 灵活性较差。因为每一个被代理类都需要有一个对应的代理类,如果接口增加了新的方法,那么代理类和被代理类都需要进行修改。例如,Subject接口新增了一个doAnotherThing方法,那么RealSubjectProxySubject都要实现这个新方法,并且在ProxySubject的新方法中也要考虑是否添加额外逻辑等情况。
  • 动态代理
    • 灵活性高。可以在运行时根据需要动态地生成代理对象,并且可以对不同的接口实现类使用同一个代理逻辑。只要这些类实现了相同的接口,就可以通过相同的InvocationHandler来处理。比如,有多个实现了Subject接口的类,如RealSubject1RealSubject2等,都可以通过同一个动态代理的InvocationHandler来进行代理,在invoke方法中可以根据传入的真实对象来灵活地处理方法调用。

三、性能对比

  • 静态代理
    • 性能相对较好。因为在编译时期已经确定了代理关系,在调用方法时,直接调用代理类的方法,不需要像动态代理那样通过反射等复杂机制来查找和调用方法。静态代理的方法调用类似于普通的对象方法调用,所以执行效率相对较高。
  • 动态代理
    • 性能稍差。由于动态代理是在运行时利用反射机制来生成代理对象和处理方法调用,反射操作本身会带来一定的性能开销。每次调用代理对象的方法时,都要经过invoke方法进行处理,包括方法查找、参数传递等操作,这些都会导致性能比静态代理稍低。

四、代码复杂度对比

静态代理

  • 代码结构相对简单,容易理解。代理类和被代理类的关系明确,代码编写和维护相对比较直观。例如,在实现ProxySubject代理RealSubject时,代码逻辑主要集中在代理类中对被代理类方法的调用和额外逻辑的添加,层次比较清晰。

  • 动态代理
    • 代码复杂度较高。需要理解反射机制、InvocationHandler接口和Proxy类的使用。特别是在InvocationHandlerinvoke方法中,要正确地处理各种方法调用情况,包括参数传递、返回值处理等,对于初学者来说,理解和编写正确的动态代理代码有一定的难度。
  • ​​​​​​z

二、动态代理的底层原理是什么? 

  1. 反射机制基础
    • 动态代理的底层核心是 Java 的反射机制。反射允许程序在运行时检查和修改类、接口、字段和方法等元素。在动态代理中,java.lang.reflect.Proxy类是关键。这个类提供了newProxyInstance方法来创建动态代理对象。
    • 例如,在动态代理的示例代码中,Proxy.newProxyInstance方法的参数包括类加载器(ClassLoader)、接口数组(Class<?>[])和InvocationHandler实例。
    • 类加载器用于加载代理对象的字节码,它负责从类文件或其他资源中读取字节码并将其转换为Class对象。接口数组指定了代理对象要实现的接口,这决定了代理对象可以调用哪些方法。InvocationHandler则是代理逻辑的核心处理程序。
  2. InvocationHandler 接口
    • 当通过动态代理对象调用方法时,实际上是调用了InvocationHandler接口的invoke方法。invoke方法有三个参数:Object proxy(代理对象本身)、Method method(被调用的方法对象)和Object[] args(方法的参数数组)。
    • 例如,在之前的动态代理代码中,DynamicProxyHandler类实现了InvocationHandler接口。当调用代理对象的doSomething方法时,实际上是调用了DynamicProxyHandler类中的invoke方法。在invoke方法内部,可以根据method参数获取被调用方法的信息,如方法名、参数类型等。
    • 然后通过method.invoke(target, args)来调用真正的被代理对象(target)的方法,并传入参数(args)。这里的method.invoke就是反射机制的应用,它在运行时根据方法对象来调用对应的方法。
  3. 代理对象的生成
    • Proxy.newProxyInstance方法在内部会根据传入的接口数组和其他信息,通过字节码生成技术动态地创建一个代理类的字节码。这个字节码在运行时被加载并实例化为代理对象。
    • 代理类实现了指定的接口,并且每个接口方法的实现逻辑都被转发到InvocationHandlerinvoke方法中。从外部看,代理对象就像是实现了目标接口的普通对象,可以直接调用接口中的方法。但在内部,这些方法调用都被重定向到invoke方法进行处理,从而实现了代理逻辑的动态插入。
    • 例如,对于一个实现了Subject接口的动态代理对象,当调用doSomething方法时,JVM 会找到代理类中对应的方法实现(这个实现是由Proxy类生成的),然后这个实现会调用InvocationHandlerinvoke方法,由invoke方法来决定如何处理这个doSomething方法的调用,包括是否执行真正的被代理对象的doSomething方法,以及在前后添加其他逻辑。

三、静态代理的底层原理是什么?

  1. 基于接口或抽象类的继承关系
    • 静态代理要求代理类和被代理类实现相同的接口或者继承相同的抽象类。这是静态代理的基础架构。
    • 例如,假设有接口Subject,被代理类RealSubject和代理类ProxySubject都实现了Subject接口。这种接口的一致性使得代理类能够在逻辑上代替被代理类接收并处理方法调用。
    • 从编译器的角度看,因为它们都遵循相同的接口契约,所以在代码中可以将代理对象和被代理对象相互替换使用,只要调用的是接口中定义的方法。
  2. 方法调用的委托机制
    • 在静态代理中,代理类持有被代理类的一个引用。当外部对象调用代理类的方法时,代理类的方法内部会通过这个引用去调用被代理类的相应方法。
    • 以之前提到的Subject接口为例,在ProxySubject类中通常会有一个RealSubject类型的成员变量,比如private RealSubject realSubject;。在ProxySubject的方法实现中,像doSomething方法可能会这样写:
@Override
public void doSomething() {
    // 可以在这里添加前置逻辑,如权限检查、日志记录等
    System.out.println("Before calling real subject.");
    realSubject.doSomething();
    System.out.println("After calling real subject.");
    // 也可以在这里添加后置逻辑
}
  • 这样,代理类就像是一个中转站,它先处理自己的逻辑(如前置和后置操作),然后将方法调用委托给被代理类。这种委托机制是静态代理实现的核心,通过这种方式,代理类能够在不改变被代理类原有功能的基础上,为其添加额外的功能。
  1. 编译时确定代理关系
    • 与动态代理不同,静态代理在编译时期就确定了代理类和被代理类的关系。编译器会根据代码中的定义生成代理类和被代理类的字节码,并且在字节码层面就已经明确了它们之间的调用关系。
    • 例如,在上面的Subject接口及其实现类和代理类的例子中,当编译ProxySubjectRealSubject这两个类时,编译器会按照代码中的逻辑生成字节码。在运行时,这种字节码的结构就决定了代理类如何调用被代理类,并且不会再发生改变,除非重新编译代码。

http://www.kler.cn/a/374915.html

相关文章:

  • 【OpenGL】知识点
  • Elasticsearch —— ES 环境搭建、概念、基本操作、文档操作、SpringBoot继承ES
  • Unity性能优化(简略版)
  • 深度学习:yolov3的使用--建立模型
  • python 爬虫 入门 五、抓取图片、视频
  • 忘记无线网络密码的几种解决办法
  • unity后端kbengine用DOTween让 移动同步丝滑
  • HJ106 字符逆序
  • 发布 NPM 包时,终端显示发布成功但实际上版本并没有更新,可能是由于以下原因
  • 基于 Postman 和 Elasticsearch 测试乐观锁的操作流程
  • Java的多态
  • LEADTOOLS 版本 23 现已发布,引入了 Excel API等众多新功能!
  • 就业市场变革:AI时代,我们将如何评估人才?
  • Python之groupby()及aggregate()方法
  • 手机实时提取SIM卡打电话的信令声音-新的篇章(三、Android虚拟声卡探索)
  • 每日互动基于 Apache DolphinScheduler 从容应对ClickHouse 大数据入库瓶颈
  • 巨好看的登录注册界面源码
  • 【 纷享销客-注册安全分析报告-无验证方式导致安全隐患】
  • C++:二叉搜索树进阶
  • flink 自定义kudu connector中使用Metrics计数平均吞吐量,并推送到自定义kafkaReporter
  • DDIM扩散模型的加速采样(去噪)算法 Denoising Diffusion Implicit Models
  • windows 11 配置 kafka 使用SASL SCRAM-SHA-256 认证
  • 操作符详解
  • Java第二阶段---15异常---第三节 自定义异常
  • 【智能算法应用】秃鹰搜索算法求解二维路径规划问题
  • 适合视频搬运的素材网站推荐——短视频素材下载宝库