肝一肝设计模式【七】-- 代理模式

news/2024/11/8 3:16:23/

系列文章目录

肝一肝设计模式【一】-- 单例模式 传送门
肝一肝设计模式【二】-- 工厂模式 传送门
肝一肝设计模式【三】-- 原型模式 传送门
肝一肝设计模式【四】-- 建造者模式 传送门
肝一肝设计模式【五】-- 适配器模式 传送门
肝一肝设计模式【六】-- 装饰器模式 传送门


文章目录

  • 系列文章目录
  • 前言
  • 一、什么是代理模式
  • 二、静态代理
  • 三、动态代理
    • 1. JDK动态代理
    • 2. CGLib动态代理
  • 写在最后


前言

本节我们继续分析设计模式中的结构型模式,前文中我们已经分析了适配器模式和装饰器模式,本节我们来学习一下——代理模式。


一、什么是代理模式

代理模式(Proxy Pattern),用于在对象之间提供间接访问,在代理模式中,代理对象充当了原始对象的代表,以控制对原始对象的访问。

代理模式的主要角色:

  • 抽象主题(Subject):定义了代理类和原始类之间的公共接口,以便代理类可以替代原始类。抽象主题通常是一个接口或抽象类,其中定义了原始对象和代理对象需要实现的方法。

  • 具体主题(Real Subject):是代理模式中的原始对象,代理类所代表的对象。具体主题实现了抽象主题中定义的接口,是真正执行业务逻辑的对象。

  • 代理(Proxy):代理是客户端访问具体主题的中介。代理对象与具体主题实现相同的接口,并保存具体主题的引用。当客户端向代理对象发送请求时,代理对象会将请求转发给具体主题,并可以在请求前后添加额外的逻辑。

概念了解了以后,其实不难理解,生活当中就有很多代理模式的样例,举个栗子,韩梅梅和李雷是同学,韩梅梅饿了但又不想自己出去吃饭,就想让李雷去买回来。
在这里插入图片描述

这里李雷就相当于是代理角色。

二、静态代理

代理模式又分为静态代理和动态代理,先说静态代理

写下代码:

先定义一个顶层接口,定义买饭这件事

public interface IPerson {void findFood();
}

韩梅梅饿了要吃饭

public class HanMeiMei implements IPerson {@Overridepublic void findFood() {System.out.println("韩梅梅饿了想找点吃的");}
}

但韩梅梅有点懒,想让李雷帮忙买回来

public class LiLei implements IPerson {private HanMeiMei hanMeiMei;public LiLei(HanMeiMei hanMeiMei) {this.hanMeiMei = hanMeiMei;}@Overridepublic void findFood() {System.out.println("李雷接到韩梅梅的电话");hanMeiMei.findFood();System.out.println("李雷帮韩梅梅买点吃的回来");}
}

测试一下:

public class Test {public static void main(String[] args) {LiLei liLei = new LiLei(new HanMeiMei());liLei.findFood();}
}

静态代理是在编译时就已经确定代理关系的代理模式。它需要为每个原始对象编写一个代理类,代理类与原始类实现相同的接口,以便可以通过代理类访问原始对象。在代理类中,可以通过调用原始对象的方法来实现对原始对象的访问,并可以在方法前后添加额外的逻辑。

三、动态代理

上述场景里,韩梅梅的主要诉求是饿了想吃饭,如果当时李雷没联系上,就还得联系别人,代码角度就还需要新增一个代理类,这样的话显然使用静态代理就不太适合了,目的是买到食物,具体谁来买其实并不重要,这就引申出来动态代理的概念。

在Java中,目前普遍使用的是JDK动态代理和CGLib动态代理

1. JDK动态代理

JDK动态代理是指使用Java内置的反射机制来动态生成代理对象的一种代理模式实现方式。

JDK动态代理需要满足以下两个条件:

  • 被代理类必须实现至少一个接口。

  • 代理类必须实现InvocationHandler接口。

InvocationHandler接口中只定义了一个方法invoke(),这个方法会在代理对象调用方法时被自动调用。在invoke()方法中,我们可以根据方法名和参数类型等信息,决定是否要将方法调用转发给被代理对象,或者在调用前后添加一些额外的逻辑。

我们来修改下代码:

首先先修改一下代理类

public class WaiMai implements InvocationHandler {private HanMeiMei hanMeiMei;public WaiMai(HanMeiMei hanMeiMei) {this.hanMeiMei = hanMeiMei;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("拿起手机");Object result = method.invoke(hanMeiMei, args);System.out.println("在外卖平台下单,小哥开始配送");return result;}
}

测试一下:

public class Test {public static void main(String[] args) {HanMeiMei hanMeiMei = new HanMeiMei();IPerson proxyPerson = (IPerson) Proxy.newProxyInstance(hanMeiMei.getClass().getClassLoader(),hanMeiMei.getClass().getInterfaces(),new WaiMai(hanMeiMei));proxyPerson.findFood();}
}

newProxyInstance()方法会动态生成一个代理类,该方法有三个参数:

  • ClassLoader:生成一个类, 这个类也需要加载到方法区中, 因此需要指定ClassLoader来加载该类
  • Class[] interfaces:要实现的接口
  • InvocationHandler:调用处理器

2. CGLib动态代理

CGLib动态代理是指使用CGLib库来动态生成代理对象的一种代理模式实现方式。
相比于JDK动态代理,它可以代理没有实现任何接口的类,因为它是通过继承被代理类来生成代理对象的。

CGLib动态代理的实现过程是:通过ASM字节码框架直接将代理对象类的class文件加载到JVM中,修改其字节码生成子类,子类重写父类中的方法,并在重写的方法中增加了我们定义的逻辑。最后,生成一个新的代理对象子类的class文件,然后通过反射机制来创建代理对象。

修改下代码:

首先修改一下代理类,不在需要实现顶层接口

public class HanMeiMei {public void findFood() {System.out.println("韩梅梅饿了想找点吃的");}
}

先新增一个实现MethodInterceptor接口的实现类

public class WaiMai implements MethodInterceptor {@Overridepublic Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {System.out.println("拿起手机");Object result = proxy.invokeSuper(obj, args);System.out.println("在外卖平台下单,小哥开始配送");return result;}
}

测试一下:

public class Test {public static void main(String[] args) {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(HanMeiMei.class);enhancer.setCallback(new WaiMai());HanMeiMei proxyHanMeiMei = (HanMeiMei) enhancer.create();proxyHanMeiMei.findFood();}
}

CGLib动态代理,可以选择使用反射调用或者FastClass机制调用,默认情况下会使用反射调用,如果需要使用FastClass机制调用,则需要通过设置Enhancer类的useFastClass属性来开启。

使用FastClass机制:

public class Test {public static void main(String[] args) {Enhancer enhancer = new Enhancer();enhancer.setSuperclass(HanMeiMei.class);enhancer.setCallback(new WaiMai());enhancer.setUseFactory(false);  // 禁用缓存,强制使用FastClass机制enhancer.setUseFastClass(true); // 开启FastClass机制HanMeiMei proxyHanMeiMei = (HanMeiMei) enhancer.create();proxyHanMeiMei.findFood();}
}

使用FastClass机制调用时,CGLib会通过ASM字节码框架生成一个FastClass类,该类中包含被代理类中方法的索引和对应的方法调用代码,这样在调用代理类的方法时,就可以直接使用FastClass类中对应方法的调用代码,避免了反射调用的开销。
需要注意的是,使用FastClass机制调用虽然能够提高代理类的性能,但也会增加代理类生成的时间和内存开销。

上文所描述的是CGLib 3.0.0 版本之前,想开启 FastClass 机制,需要手动调用setUseFastClass()方法来设置。
CGLib 3.0.0 版本开始默认启用 FastClass 机制,无需调用setUseFastClass()方法。


写在最后

代理模式的基本思想是创建一个代理对象,该代理对象与原始对象具有相同的接口,以便可以替换原始对象。当客户端向代理对象发送请求时,代理对象会将请求转发给原始对象,同时可以在请求前后添加额外的逻辑。这种方式可以隐藏原始对象的复杂性,并提供更加简单和易用的接口。

代理模式的优点:

  • 解耦原有对象,代理对象与原有对象之间的耦合度降低,原有对象可以专注于自己的业务逻辑,而代理对象则负责其他方面的操作。
  • 对原有对象进行增强,代理对象可以在调用原有对象方法前后进行一些操作,例如日志记录、缓存处理等,从而增强了原有对象的功能。

静态代理的优点:

  • 在编译时进行类型检查,避免了运行时出现类型错误的风险
  • 提供更好的性能,因为代理类在编译时就已经生成,不需要在运行时动态生成代理对象

静态代理的缺点:

  • 需要为每个原始对象编写一个代理类,如果原始对象的接口发生变化,代理类也需要相应地进行更新

动态代理的优点:

  • 可以动态地生成代理对象,避免了静态代理中需要为每个原始对象编写代理类的麻烦
  • 可以支持对不同的原始对象进行代理,并可以在运行时动态地添加或删除代理对象

动态代理的缺点:

  • JDK动态代理和CGLib动态代理(反射调用)需要通过反射机制动态生成代理类,可能会降低一些性能,CGLib动态代理(FastClass调用)虽然避免了反射调用的开销,但会增加代理类生成的时间和内存开销,同样会影响性能
  • 只能代理公共方法,不能代理私有方法和final方法

http://www.ppmy.cn/news/64108.html

相关文章

【shell脚本里的数组】

目录 一、数组的定义方法1、定义数组1.1、使用shell脚本来写出怎么查看这个数组有没有缺少元素1.2、使用shell脚本,来生成数组1.3、元素的切片 二、数组追加元素三、传输和传出1、向函数传入数组的值2、从函数返回数组 一、数组的定义方法 ( 20 10 60…

企业布局新媒体矩阵,如何走得更远?

企业搭建新媒体矩阵有很多好处——扩大品牌声量、丰富内容形式、提高宣传效率、降低运营风险、节省广告成本...... 即便如此,能真正让新媒体矩阵产生如此效果的企业,却是凤毛麟角。 更多的企业,往往冒然入场,也黯然离场&#xff0…

【iOS】---pthread,NSThread

在iOS中多线程开发有四种方式,在之前我们浅浅了解了一下GCD,这期来看看pthread和NSThread pehread pthread简介 pthread 是一套通用的多线程的 API,可以在Unix / Linux / Windows 等系统跨平台使用,使用 C 语言编写,…

头歌机器学习---决策树

第1关:什么是决策树: 如何构造出一棵好的决策树呢?其实构造决策树时会遵循一个指标,有的是按照信息增益来构建,如ID3算法;有的是信息增益率来构建,如C4.5算法;有的是按照基尼系数来构…

Python并发编程之进程理论

前言 本文将详细介绍进程相关概念。 进程和程序 计算机上的未运行的QQ、Wechat等都属于程序,但是一旦当这些程序运行起来的话,就可以被称为进程。因此可以如下定义程序和进程: 程序:就是存在硬盘上的一堆代码。 进程&#xf…

pytest+allure实现接口自动化框架(1、yml管理case 2、变量的处理 3、参数的传递 4、相互依赖接口的处理)

目录 前言: 1.1 源码: 1.2 实现功能 1.3 yml管理case功能 框架详细介绍: 一、使用git远程连接&上传&下载项目 1.1 用户权限问题:本地生成密钥,gitlab后台端绑定公钥 1.1.1 本地生成密钥 1.2 项目里没有…

scratch绘制直尺 中国电子学会图形化编程 少儿编程 scratch编程等级考试四级真题和答案解析2023年3月

目录 scratch绘制直尺 一、题目要求 1、准备工作 2、功能实现 二、案例分析

QT中的模态对话框及非模态对话框

QT中的模态对话框及非模态对话框 [1] QT中的模态对话框及非模态对话框[2] Qt工作笔记-主界面往模式对话框emit信号,有注意的问题正常情况下:不正常情况下:下面给出正常情况下的代码: [1] QT中的模态对话框及非模态对话框 原文链接…