代理模式和Java中的动态代理【开发实践】

文章目录

    • 一、代理模式基础
      • 1.1 代理模式
      • 1.2 静态代理
      • 1.3 动态代理
    • 二、静态代理的实现
    • 三、JDK动态代理
      • 3.1 JDK动态代理概述
      • 3.2 invoke方法介绍
      • 3.3 JDK动态代理的使用
    • 四、CGLIB动态代理
      • 3.1 CGLIB动态代理概述
      • 3.2 CGLIB动态代理的使用
    • 五、对比
      • 5.1 代理实现与使用对比
      • 5.2 使用条件对比
      • 5.3 增强逻辑的位置对比

一、代理模式基础

1.1 代理模式

代理模式是常见的设计模式之一,代理模式就是代理对象具备真实对象的功能,能代替真实对象完成相应操作,并能在操作执行的前后进行增强处理。

代理模式常应用于面向切面编程(AOP)、日志记录、权限控制、性能监控等多种横切关注点的系统级服务。

代理模式的实现可以分为两类:一类是静态代理,另一类是动态代理。

1.2 静态代理

静态代理在程序运行前就已经存在,指由程序员需要手动编写或使用特定工具生成代理类的源代码(即.class 文件),在程序编译阶段就已确定。

静态代理需要为每个目标类手动编写代理类,可能导致代码重复。适用于代码结构相对固定且代理类数量有限的场景。

1.3 动态代理

动态代理是在程序运行期间动态生成的。代理不是直接写成 .class 文件,而是在JVM运行时通过反射、字节码操作(如Java的 java.lang.reflect.Proxy 类或第三方库如CGLIB)等机制动态构建的。

动态代理提供了更高的灵活性和扩展性,但会牺牲少许性能。

二、静态代理的实现

先理解静态代理的实现,更容易理解JDK动态代理的原理,其使用流程如下:

  1. 定义一个接口,里面有需要被代理/增强的方法。
  2. 定义一个需要被代理的类,实现上述接口。
  3. 定义一个静态代理类,也实现上述接口,并且需要定义接口类型的成员变量,用于指向被代理的对象。在静态代理类中,可以对接口方法进行增强,内部可以调用被代理对象的对应方法,并在前后做增强处理。
  4. 使用时,用接口申明变量类型,指向静态代理类的实例对象。
// 接口
interface Subject {
	// 需要被增强的方法
    void request();
}

// 实现类
class RealSubject implements Subject {
    @Override
    public void request() {
        System.out.println("RealSubject: Handling request.");
    }
}

// 静态代理类
class StaticProxy implements Subject {
	// 成员变量用于指向被代理的对象,使用接口类型申明该变量
    private final Subject realSubject;

	// 可以用构造器或者set方法来设置被代理的对象
    public StaticProxy(Subject realSubject) {
        this.realSubject = realSubject;
    }

    @Override
    public void request() {
    	// 可选的前置处理
        preRequest();
        // 调用被代理的对象
        realSubject.request();
        // 可选的后置处理
        postRequest();
    }

    private void preRequest() {
        System.out.println("StaticProxy: Pre-processing request.");
    }

    private void postRequest() {
        System.out.println("StaticProxy: Post-processing request.");
    }
}

// 使用静态代理对象
public class StaticProxyTest {
    public static void main(String[] args) {
    	// 使用接口申明变量类型,指向静态代理类的实例对象
        Subject subject = new StaticProxy(new RealSubject());
        subject.request();
    }
}

三、JDK动态代理

3.1 JDK动态代理概述

JDK动态代理是一种利用Java反射机制在运行时动态创建代理对象的技术,它是Java原生支持的,主要依赖于java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口。

JDK 动态代理基于接口,只有实现了接口的类,才能通过 JDK 动态代理来增强接口方法。

3.2 invoke方法介绍

  • Object proxy:代理对象的引用。尽管在invoke方法内部可能不需要直接使用它,但它代表了代理实例本身,有时候可用于获取代理类的信息或其他特殊处理。
  • Method method:表示被调用的方法的一个Method对象。这个对象包含了方法的所有元数据,如方法名、返回类型、参数类型等。你可以利用这个对象来判断被调用的是哪个具体方法,从而做出不同的处理逻辑。
  • Object[] args:一个对象数组,包含了调用方法时传递的实际参数。这些参数与原始方法调用时提供的参数类型和顺序完全一致。你可以利用这些参数来传递给被代理方法,或者进行预处理/后处理
  • 返回值:invoke方法的返回值会作为实际调用方法的返回值。

3.3 JDK动态代理的使用

JDK动态代理的使用流程如下:

  1. 定义一个接口,里面有需要被代理/增强的方法。
  2. 定义一个需要被代理的类,实现上述接口。
  3. 定义一个动态代理处理器,需要实现InvocationHandler接口,并且需要定义接口类型的成员变量,用于指向被代理的对象。实现invoke()方法,可以对接口方法进行增强。
  4. 使用时,用接口申明变量类型,指向Proxy.newProxyInstance()返回的代理对象。
// 动态代理处理器
class DynamicProxyHandler implements InvocationHandler {

    private final Subject realSubject;

    public DynamicProxyHandler(Subject realSubject) {
        this.realSubject = realSubject;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        preRequest();
        // 调用被代理对象的方法
        // 调用接口方法时,这里就会调用被代理对象的相应方法
        Object result = method.invoke(realSubject, args);
        postRequest();
        return result;
    }

    private void preRequest() {
        System.out.println("DynamicProxyHandler: Pre-processing request.");
    }

    private void postRequest() {
        System.out.println("DynamicProxyHandler: Post-processing request.");
    }
}

// 测试动态代理
public class DynamicProxyTest {
    public static void main(String[] args) {
        Subject realSubject = new RealSubject();
        // 返回的是Object对象,需要转换为目标接口对象
        Subject proxySubject = (Subject) Proxy.newProxyInstance(
                RealSubject.class.getClassLoader(),
                new Class[]{Subject.class},
                new DynamicProxyHandler(realSubject)
        );
        proxySubject.request();
    }
}

四、CGLIB动态代理

3.1 CGLIB动态代理概述

CGLIB(Code Generation Library)是一个强大的、高性能代码生成库,它允许在运行时动态地生成修改Java字节码

CGLIB动态代理基于子类继承,能够对任何类(无论是否实现了接口)实现代理,这使得它的应用范围更广。

3.2 CGLIB动态代理的使用

CGLIB动态代理的使用流程如下:

  1. 定义目标类(无需实现接口)。
  2. 定义方法拦截器,需要实现MethodInterceptor接口。实现intercept()方法来对被代理对象的方法进行增强。
  3. 使用时,需要创建方法拦截器对象和Enhancer对象,通过Enhancer对象来返回代理对象。该操作可以封装在方法拦截器中。
// 目标类,无需实现接口
class RealSubject {
    public void request() {
        System.out.println("RealSubject: Handling request.");
    }
}

// CGLIB代理实现
class CglibProxy implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        preRequest();
        Object result = proxy.invokeSuper(obj, args); // 调用父类方法,即真实对象的方法
        postRequest();
        return result;
    }

    private void preRequest() {
        System.out.println("CglibProxy: Pre-processing request.");
    }

    private void postRequest() {
        System.out.println("CglibProxy: Post-processing request.");
    }

    // 创建代理对象
    public Object getProxy(Class<?> clazz) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(clazz); // 设置父类
        enhancer.setCallback(this); // 设置回调方法
        return enhancer.create(); // 创建并返回代理对象
    }
}

// 测试CGLIB动态代理
public class CglibProxyTest {
    public static void main(String[] args) {
        CglibProxy cglibProxy = new CglibProxy();
        // 返回的是Object对象,需要转换为被代理类的对象
        RealSubject proxySubject = (RealSubject) cglibProxy.getProxy(RealSubject.class);
        proxySubject.request();
    }
}

五、对比

5.1 代理实现与使用对比

实现代理逻辑
都需要调用目标方法,然后在目标方法前后新增增强逻辑。区别是,静态代理在实现接口方法时,调用被代理对象的目标方法并实现增强。动态代理需要实现接口(InvocationHandler / MethodInterceptor),必须实现接口方法(invoke / intercept),在接口方法里调用被代理对象的目标方法并实现增强。
获取代理对象
静态代理时,被代理类的对象即代理对象。
JDK动态代理中,使用Proxy.newProxyInstance()来获取代理对象。
CGLIB动态代理中,使用enhancer.create()来获取代理对象。

5.2 使用条件对比

静态代理和CGLIB动态代理都依赖于接口,CGLIB动态代理不依赖于接口。

5.3 增强逻辑的位置对比

静态代理中,增强逻辑分散在被代理类的各个方法中,需要在方法中实现该方法的增强逻辑。如果这些方法都需要同种增强,将产生大量的重复代码。

在动态代理中,增强逻辑集中在一个方法里。如果所有方法都需要同种增强,增强逻辑只需要写一处,非常方便。如果方法的增强逻辑不同,则需要通过反射获取方法名,然后使用响应的增强逻辑。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/777835.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

54、一维和二维自组织映射(matlab)

1、一维和二维自组织映射原理 一维和二维自组织映射&#xff08;Self-Organizing Maps, SOM&#xff09;是一种无监督的机器学习算法&#xff0c;通过学习输入数据的拓扑结构&#xff0c;将高维输入数据映射到低维的网格结构中&#xff0c;使得相似的输入数据点在映射空间中也…

win7系统快速安装python

下载安装包 建议选择python3.8左右的&#xff0c;我下载的是3.7.8&#xff0c;最新版本的pythonwin7可能不支持 python网址 下拉寻找 安装python 1.双击安装包 更换完地址选择安装(install) 安装完成后点击close即可 测试是否安装成功 1.winr快捷键打开黑窗口输入cmd …

七大排序-冒泡排序,插入排序,希尔排序(一)

目录 排序冒泡排序插入排序冒泡排序和插入排序的对比希尔排序 排序 先写单趟&#xff0c;再写多趟&#xff0c;这样比较好写 排序可以理解为对商品价格的排序&#xff0c;对数字大小的排序&#xff0c;排序再生活中随处可见 冒泡排序 冒泡排序就是两个相邻的数交换&#xff…

跨界客户服务:拓展服务边界,创造更多价值

在当今这个日新月异的商业时代&#xff0c;跨界合作已不再是新鲜词汇&#xff0c;它如同一股强劲的东风&#xff0c;吹散了行业间的壁垒&#xff0c;为企业服务创新开辟了前所未有的广阔天地。特别是在客户服务领域&#xff0c;跨界合作正以前所未有的深度和广度&#xff0c;拓…

mysql 9 新特新

mysql9新特性 新特性Audit Log NotesC API NotesCharacter Set SupportCompilation NotesComponent NotesConfiguration NotesData Dictionary NotesData Type NotesDeprecation and Removal NotesEvent Scheduler NotesJavaScript ProgramsOptimizer NotesPerformance Schema …

微机原理与单片机 知识体系梳理

单片机笔记分享 我个人感觉单片机要记的东西很多&#xff0c;也很琐碎&#xff0c;特别是一些位、寄存器以及相关作用等&#xff0c;非常难以记忆。因此复习时将知识点整理在了一起做成思维导图&#xff0c;希望对大家有所帮助。内容不是很多&#xff0c;可能有些没覆盖全&…

Python人形机踊跃跨栏举重投篮高维数动作算法模型

&#x1f3af;要点 &#x1f3af;运动功能&#xff1a; 1 m / s 1 m / s 1m/s上台阶、站立平衡、 1 m / s 1 m / s 1m/s行走、坐椅子、 5 m / s 5 m / s 5m/s跑步、 1 m / s 1 m / s 1m/s爬行、穿越森林、取物、穿越迷宫、 1 m / s 1 m / s 1m/s上滑梯、 5 m / s 5 m / s 5m/s…

iOS多target时怎么对InfoPlist进行国际化

由于不同target要显示不同的App名称、不同的权限提示语&#xff0c;国际化InfoPlist文件必须创建名称为InfoPlist.strings的文件&#xff0c;那么多个target时怎么进行国际化呢&#xff1f;步骤如下&#xff1a; 一、首先我们在项目根目录创建不同的文件夹对应多个不同的targe…

自然之美无需雕琢

《自然之美&#xff0c;无需雕琢 ”》在这个颜值至上的时代&#xff0c;但在温馨氛围中&#xff0c;单依纯以一种意想不到的方式&#xff0c;为我们诠释了自然之美的真谛。而医生的回答&#xff0c;如同一股清流耳目一新。“我说医生你看我这张脸&#xff0c;有没有哪里要动的。…

09 docker 安装tomcat 详解

目录 一、安装tomcat 1. tomcat镜像的获取 2. docker创建容器实列 3. 访问测试 404错误 4. 解决方案 5. 使用免修改版容器镜像 5.1. 运行实列的创建 5.2. 出现问题及解决&#xff1a; 6. 验证 OK 一、安装tomcat 1. tomcat镜像的获取 docker search tomcat #docker …

最灵活且易用的C++开源串口通信调试软件

这款C开源串口调试软件&#xff0c;集成了丰富的功能&#xff0c;为用户提供高效、便捷的串口通信调试体验。以下是其核心功能亮点&#xff1a; 基础功能&#xff1a; 数据交互自如&#xff1a;支持串口数据的轻松读取与发送&#xff0c;让数据交换变得简单直接。 灵活配置参…

【后端面试题】【中间件】【NoSQL】MongoDB查询优化3(拆分、嵌入文档,操作系统)

拆分大文档 很常见的一种优化手段&#xff0c;在一些特定的业务场景中&#xff0c;会有一些很大的文档&#xff0c;这些文档有很多字段&#xff0c;而且有一些特定的字段还特别的大。可以考虑拆分这些文档 大文档对MongoDB的性能影响还是很大的&#xff0c;就我个人经验而言&…

【TB作品】基于ATmega48的开机登录程序设计

使用Proteus仿真软件设计一个开机登录程序,单片机选用ATmegga48. 基础要求: 1.程序启动后在LCD1602液晶屏上提示用户通过独立按键输入密码(6位)。 2.密码输入错误则在屏幕上提示密码错误,密码输入正确则在屏幕上提示密 码正确后等待约3秒后进入主界面,在屏幕中央显示HelloWorld…

基于RK3588的8路摄像头实时全景拼接

基于RK3588的8路摄像头实时全景拼接 输入&#xff1a;2路csi转8路mpi的ahd摄像头&#xff0c;分辨率1920 * 1080 8路拼接结果&#xff1a; 6路拼接结果&#xff1a; UI界面&#xff1a; UI节目设计原理

数字时代如果你的企业还未上线B端系统助力则后果很严重

**数字时代如果你的企业还未上线B端系统助力则后果很严重** 数字化浪潮席卷全球&#xff0c;企业对于数字化转型的重视程度日益提高。B端系统&#xff0c;作为企业数字化转型的核心组成部分&#xff0c;其重要性不言而喻。如果你的企业还未上线B端系统助力&#xff0c;那么后果…

异步主从复制

主从复制的概念 主从复制是一种在数据库系统中常用的数据备份和读取扩展技术&#xff0c;通过将一个数据库服务器&#xff08;主服务器&#xff09;上的数据变更自动同步到一个或多个数据库服务器&#xff08;从服务器&#xff09;上&#xff0c;以此来实现数据的冗余备份、读…

2024年6月后2周重要的大语言模型论文总结:LLM进展、微调、推理和对齐

本文总结了2024年6月后两周发表的一些最重要的大语言模型论文。这些论文涵盖了塑造下一代语言模型的各种主题&#xff0c;从模型优化和缩放到推理、基准测试和增强性能。 LLM进展与基准 1、 BigCodeBench: Benchmarking Code Generation with Diverse Function Calls and Com…

图文识别0难度上手~基于飞浆对pdf简易ocr并转txt

前言 本篇pdf适用windows对视觉识别0基础的的纯小白用户。大佬请绕道~~ 注意&#xff1a; 本项目pdf的ocr对于表格、画图文字&#xff0c;水印等干扰没做任何处理&#xff0c;因此希望各位使用该功能的pdf尽量不要含有这些干扰项&#xff0c;以免影响翻译效果。 流程 1.构建…

收银系统源码-收银台副屏广告

1. 功能描述 门店广告&#xff1a;双屏收银机&#xff0c;副屏广告&#xff0c;主屏和副屏同步&#xff0c;总部可统一控制广告位&#xff0c;也可以给门店开放权限&#xff0c;门店独立上传广告位&#xff1b; 2.适用场景 新店开业、门店周年庆、节假日门店活动宣传&#x…

Nginx实战:nginx性能压测(ab)

在nginx的生产实践中,不管是服务上线,还是性能优化,都会遇到需要对nginx的性能压测,本文介绍一个简单的压测工具:ab命令 ab(Apache Bench)是一个常用的HTTP压力测试工具,可以用来测试Nginx的性能和压力。ab命令可以指定并发请求数、请求数、请求类型等参数,并输出测试…