【设计模式】6.代理模式
概述
代理模式:为一个对象提供一个替身,以控制对这个对象的访问。即通过代理访问目标对象
这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。
被代理的对象可以是:远程对象、创建开销大的对象或需要安全控制的对象
由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。
Java中的代理按照代理类生成时机不同又分为静态代理和动态代理。静态代理代理类在编译期就生成,而动态代理代理类则是在Java运行时动态生成。动态代理又有JDK代理和CGLib代理两种。
结构
代理(Proxy)模式分为三种角色:
- 抽象主题(Subject)类: 通过接口或抽象类声明真实主题和代理对象实现的业务方法。
- 真实主题(Real Subject)类: 实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象。
- 代理(Proxy)类 : 提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。
静态代理
我们通过案例来感受一下静态代理。
【例】火车站卖票
如果要买火车票的话,需要去火车站买票,坐车到火车站,排队等一系列的操作,显然比较麻烦。而火车站在多个地方都有代售点,我们去代售点买票就方便很多了。这个例子其实就是典型的代理模式,火车站是目标对象,代售点是代理对象。类图如下:

代码如下:
//卖票接口
public interface SellTickets {void sell();
}//火车站 火车站具有卖票功能,所以需要实现SellTickets接口
public class TrainStation implements SellTickets {public void sell() {System.out.println("火车站卖票");}
}//代售点
public class ProxyPoint implements SellTickets {private TrainStation station = new TrainStation();public void sell() {System.out.println("代理点收取一些服务费用");station.sell();}
}//测试类
public class Client {public static void main(String[] args) {ProxyPoint pp = new ProxyPoint();pp.sell();}
}
从上面代码中可以看出测试类直接访问的是ProxyPoint类对象,也就是说ProxyPoint作为访问对象和目标对象的中介。同时也对sell方法进行了增强(代理点收取一些服务费用)。
动态代理
JDK动态代理
接下来我们使用动态代理实现上面案例,先说说JDK提供的动态代理。Java中提供了一个动态代理类Proxy,Proxy并不是我们上述所说的代理对象的类,而是提供了一个创建代理对象的静态方法(newProxyInstance方法)来获取代理对象。
代码如下:
//卖票接口
public interface SellTickets {void sell();
}//火车站 火车站具有卖票功能,所以需要实现SellTickets接口
public class TrainStation implements SellTickets {public void sell() {System.out.println("火车站卖票");}
}//代理工厂,用来创建代理对象
public class ProxyFactory {private TrainStation station = new TrainStation();public SellTickets getProxyObject() {//使用Proxy获取代理对象/*newProxyInstance()方法参数说明:ClassLoader loader : 类加载器,用于加载代理类,使用真实对象的类加载器即可Class<?>[] interfaces : 真实对象所实现的接口,代理模式真实对象和代理对象实现相同的接口InvocationHandler h : 代理对象的调用处理程序*/SellTickets sellTickets = (SellTickets) Proxy.newProxyInstance(station.getClass().getClassLoader(),station.getClass().getInterfaces(),new InvocationHandler() {/*InvocationHandler中invoke方法参数说明:proxy : 代理对象method : 对应于在代理对象上调用的接口方法的 Method 实例args : 代理对象调用接口方法时传递的实际参数*/public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("代理点收取一些服务费用(JDK动态代理方式)");//执行真实对象Object result = method.invoke(station, args);return result;}});return sellTickets;}
}//测试类
public class Client {public static void main(String[] args) {//获取代理对象ProxyFactory factory = new ProxyFactory();SellTickets proxyObject = factory.getProxyObject();proxyObject.sell();}
}
使用了动态代理,我们思考下面问题:
-
ProxyFactory是代理类吗?
ProxyFactory不是代理模式中所说的代理类,而代理类是程序在运行过程中动态的在内存中生成的类。通过阿里巴巴开源的 Java 诊断工具(Arthas【阿尔萨斯】)查看代理类的结构:
package com.sun.proxy;import com.itheima.proxy.dynamic.jdk.SellTickets; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException;public final class $Proxy0 extends Proxy implements SellTickets {private static Method m1;private static Method m2;private static Method m3;private static Method m0;public $Proxy0(InvocationHandler invocationHandler) {super(invocationHandler);}static {try {m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);m3 = Class.forName("com.itheima.proxy.dynamic.jdk.SellTickets").getMethod("sell", new Class[0]);m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);return;}catch (NoSuchMethodException noSuchMethodException) {throw new NoSuchMethodError(noSuchMethodException.getMessage());}catch (ClassNotFoundException classNotFoundException) {throw new NoClassDefFoundError(classNotFoundException.getMessage());}}public final boolean equals(Object object) {try {return (Boolean)this.h.invoke(this, m1, new Object[]{object});}catch (Error | RuntimeException throwable) {throw throwable;}catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}public final String toString() {try {return (String)this.h.invoke(this, m2, null);}catch (Error | RuntimeException throwable) {throw throwable;}catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}public final int hashCode() {try {return (Integer)this.h.invoke(this, m0, null);}catch (Error | RuntimeException throwable) {throw throwable;}catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}}public final void sell() {try {this.h.invoke(this, m3, null);return;}catch (Error | RuntimeException throwable) {throw throwable;}catch (Throwable throwable) {throw new UndeclaredThrowableException(throwable);}} }从上面的类中,我们可以看到以下几个信息:
- 代理类($Proxy0)实现了SellTickets。这也就印证了我们之前说的真实类和代理类实现同样的接口。
- 代理类($Proxy0)将我们提供了的匿名内部类对象传递给了父类。
-
动态代理的执行流程是什么样?
下面是摘取的重点代码:

//程序运行过程中动态生成的代理类 public final class $Proxy0 extends Proxy implements SellTickets {private static Method m3;public $Proxy0(InvocationHandler invocationHandler) {super(invocationHandler);}static {m3 = Class.forName("com.itheima.proxy.dynamic.jdk.SellTickets").getMethod("sell", new Class[0]);}public final void sell() {this.h.invoke(this, m3, null);} }//Java提供的动态代理相关类 public class Proxy implements java.io.Serializable {protected InvocationHandler h;protected Proxy(InvocationHandler h) {this.h = h;} }//代理工厂类 public class ProxyFactory {private TrainStation station = new TrainStation();public SellTickets getProxyObject() {SellTickets sellTickets = (SellTickets) Proxy.newProxyInstance(station.getClass().getClassLoader(),station.getClass().getInterfaces(),new InvocationHandler() {public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("代理点收取一些服务费用(JDK动态代理方式)");Object result = method.invoke(station, args);return result;}});return sellTickets;} }//测试访问类 public class Client {public static void main(String[] args) {//获取代理对象ProxyFactory factory = new ProxyFactory();SellTickets proxyObject = factory.getProxyObject();proxyObject.sell();} }
执行流程如下:
- 在测试类中通过代理对象调用sell()方法
- 根据多态的特性,执行的是代理类($Proxy0)中的sell()方法
- 代理类($Proxy0)中的sell()方法中又调用了InvocationHandler接口的子实现类对象的invoke方法
- invoke方法通过反射执行了真实对象所属类(TrainStation)中的sell()方法
CGLIB动态代理
同样是上面的案例,我们再次使用CGLIB代理实现。
如果没有定义SellTickets接口,只定义了TrainStation(火车站类)。很显然JDK代理是无法使用了,因为JDK动态代理要求必须定义接口,对接口进行代理。
CGLIB是一个功能强大,高性能的代码生成包。它为没有实现接口的类提供代理,为JDK的动态代理提供了很好的补充。
CGLIB是第三方提供的包,所以需要引入jar包的坐标:
<dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>2.2.2</version>
</dependency>
代码如下:
//火车站
public class TrainStation {public void sell() {System.out.println("火车站卖票");}
}//代理工厂
public class ProxyFactory implements MethodInterceptor {private TrainStation target = new TrainStation();public TrainStation getProxyObject() {//创建Enhancer对象,类似于JDK动态代理的Proxy类,下一步就是设置几个参数Enhancer enhancer =new Enhancer();//设置父类的字节码对象enhancer.setSuperclass(target.getClass());//设置回调函数enhancer.setCallback(this);//创建代理对象TrainStation obj = (TrainStation) enhancer.create();return obj;}/*intercept方法参数说明:o : 代理对象method : 真实对象中的方法的Method实例args : 实际参数methodProxy :代理对象中的方法的method实例*/public TrainStation intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {System.out.println("代理点收取一些服务费用(CGLIB动态代理方式)");TrainStation result = (TrainStation) methodProxy.invokeSuper(o, args);return result;}
}//测试类
public class Client {public static void main(String[] args) {//创建代理工厂对象ProxyFactory factory = new ProxyFactory();//获取代理对象TrainStation proxyObject = factory.getProxyObject();proxyObject.sell();}
}
三种代理的对比
-
jdk代理和CGLIB代理
使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,在JDK1.6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的类或者方法进行代理,因为CGLib原理是动态生成被代理类的子类。
在JDK1.6、JDK1.7、JDK1.8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLib代理效率,只有当进行大量调用的时候,JDK1.6和JDK1.7比CGLib代理效率低一点,但是到JDK1.8的时候,JDK代理效率高于CGLib代理。所以如果有接口使用JDK动态代理,如果没有接口使用CGLIB代理。
-
动态代理和静态代理
动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。
如果接口增加一个方法,静态代理模式除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。而动态代理不会出现该问题
优缺点
优点:
- 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
- 代理对象可以扩展目标对象的功能;
- 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度;
缺点:
- 增加了系统的复杂度;
使用场景
-
远程(Remote)代理
本地服务通过网络请求远程服务。为了实现本地到远程的通信,我们需要实现网络通信,处理其中可能的异常。为良好的代码设计和可维护性,我们将网络通信部分隐藏起来,只暴露给本地服务一个接口,通过该接口即可访问远程服务提供的功能,而不必过多关心通信部分的细节。
-
防火墙(Firewall)代理
当你将浏览器配置成使用代理功能时,防火墙就将你的浏览器的请求转给互联网;当互联网返回响应时,代理服务器再把它转给你的浏览器。
-
保护(Protect or Access)代理
控制对一个对象的访问,如果需要,可以给不同的用户提供不同级别的使用权限。
相关文章:
【设计模式】6.代理模式
概述 代理模式:为一个对象提供一个替身,以控制对这个对象的访问。即通过代理访问目标对象 这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。 被代理的对象可以是:远程对象、创建开销大的对象或需要安全…...
SRC挖掘之Access验证校验的漏洞挖掘
漏洞已修复,感谢某大佬的知识分享。 任意用户密码重置->可获取全校师生个人mingan信息 开局就是信息收集。 对于挖掘edu的信息收集 1.可尝试谷歌搜索语法,获取学号信息 2. 旁站的渗透获取 3. 学校的贴吧获取(大部分都是本校学生) 当然我就是闲&a…...
GG-21 100V 5A逆功率继电器
1 用途 GG-21逆功率继电器在出现逆功率时,从电网中断开交流发电机。 2 概述 逆功率继电器是基于感应式原理(具有旋转磁场)而工作。 继电器导磁体由两个磁路系统组成:上磁路系统和下磁路系统。电流线圈安装在上磁路系统中,它由接在发电机某相的…...
MyBatis中#{}和${}的区别
目录 前言 1、处理参数的方式不同 2、${}的优点 3、SQL注入问题 4、like查询问题 前言 #{}和${}都可以在MyBatis中用来动态地接收参数,但二者在本质上还是有很大的区别。 1、处理参数的方式不同 ${} :预编译处理 MyBatis在处理#{}时,…...
ElementUi的使用
ElementUi使用说明 element ui安装与配置 npm i element-ui –S项目入口文件main.js 导入 Element-UI 相关资源// 导入组件库 import ElementUI from element-ui; // 导入组件相关样式 import element-ui/lib/theme-chalk/index.css; // 配置 Vue 插件 Vue.use(ElementUI);文档…...
termux手机端安装mysql(MariaDB)
目录1 下载MariaDB2 配置MariaDB3 启动MariaDB服务器查看进程pid杀死进程4 登录 Mysqltermux用户登录MySQLroot用户登录MySQL5 配置 MariaDB 远程登录创建一个可远程登录的用户:用户授权:刷新授权:6 停止 MariaDB 服务器7 可选,但…...
Python枚举类定义和使用(详解版)
一些具有特殊含义的类,其实例化对象的个数往往是固定的,比如用一个类表示月份,则该类的实例对象最多有 12 个;再比如用一个类表示季节,则该类的实例化对象最多有 4 个。 针对这种特殊的类,Python 3.4 中新…...
京东HBase异地多活调研
京东HBase平台架构 HBase Replication原理 HBase的Replication是基于WAL日志文件的,在主集群中的每个RegionServer上,由ReplicationSource线程来负责推送数据,在备集群的RegionServer上由ReplicationSink线程负责接收数据。ReplicationSourc…...
【LeetCode】剑指 Offer 18. 删除链表的节点(题目一) p119 -- Java Version
题目链接:https://leetcode.cn/problems/shan-chu-lian-biao-de-jie-dian-lcof/ 1. 题目介绍(18. 删除链表的节点) 给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。 返回删除后的链表的头节点。 注意&…...
SpringMVC异步请求
背景 Tomcat等应用服务器的连接线程池实际上是有限制的;每一个连接请求都会耗掉线程池的一个连接数;如果某些耗时很长的操作,如对大量数据的查询操作、调用外部系统提供的服务以及一些 IO 密集型操作等,会占用连接很长时间&#…...
这七个100%提高Python代码性能的技巧,一定要知道
B站|公众号:啥都会一点的研究生 相关阅读 整理了几个100%会踩的Python细节坑,提前防止脑血栓 整理了十个100%提高效率的Python编程技巧,更上一层楼 Python-列表,从基础到进阶用法大总结,进来查漏补缺 Python-元组&…...
计算机网络笔记、面试八股(五)—— 浏览器输入URL
本章目录5. 从输入URL到浏览器显示页面过程中都发生了什么5.1 URL输入5.2 DNS解析5.2.1 域名的等级5.2.2 DNS解析的流程5.2.3 DNS查询方式5.3 建立TCP连接5.4 发送HTTP/HTTPS请求5.5 服务器处理请求并返回HTTP响应5.6 浏览器解析渲染页面5.7 HTTP请求结束,断开TCP连…...
【速记】快速调通算法项目的环境
1.创建新的conda环境,避免把原有的环境给搞坏。 在CMD中执行,而不是在anaconda的命令行中执行: conda create -n 环境名 --offline python3.8 2.在pycharm中配置conda环境: setting->Project Interpreter->齿轮->add-&g…...
开放开源开先河(上)
目录 1.唯一性定义品牌 2.打造爆款塑造品牌 3.构筑生态体系传播品牌 2022年7月28日,以“软件定义世界 开源共筑未来”为主题的全球数字经济大会开放原子开源峰会在北京开幕,承办主峰会和为捐赠人进行授牌仪式的开放原子开源基金会再次进入公众视野。基金…...
TencentOS 3.1安装MySQL 8.0.32
到官网下载安装包:https://dev.mysql.com/downloads/mysql/ 使用如下命令解包。 tar xf mysql-8.0.32-1.el8.x86_64.rpm-bundle.tar 使用rpm -qa |grep mysql 和rpm -qa |grep mariadb检查是否安装过mysql 如果有,使用下命令移除: rpm -e …...
Javascript的API基本内容(五)
一、js组成 JavaScript的组成 ECMAScript: 规定了js基础语法核心知识。 比如:变量、分支语句、循环语句、对象等等 Web APIs : DOM 文档对象模型, 定义了一套操作HTML文档的API BOM 浏览器对象模型,定义了一套操作浏览器窗口的API 二、loc…...
分层测试(2)单元测试【必备】
1. 什么是单元测试? 对代码中的逻辑隔离的最小代码片段进行测试,验证其逻辑是否符合预期,单元可以是函数,方法,类,功能模块。 2. 单元测试的优点 掌握代码:单元测试允许开发人员了解单元提供…...
代码随想录算法训练营day45 |动态规划之背包问题 70. 爬楼梯 (进阶) 322. 零钱兑换 279.完全平方数
day4570. 爬楼梯 (进阶)1. 确定dp数组以及下标的含义2.确定递推公式3.dp数组如何初始化4.确定遍历顺序5.举例来推导dp数组322. 零钱兑换1. 确定dp数组以及下标的含义2.确定递推公式3.dp数组如何初始化4.确定遍历顺序5.举例推导dp数组279.完全平方数1. 确…...
秒懂算法 | 基于图神经网络的推荐算法
图神经网络(Graph Neural Networks,GNN)是近几年兴起的学科,用来作推荐算法自然效果也相当好,但是要学会基于图神经网络的推荐算法之前,需要对图神经网络自身有个了解。 图卷积网络(Graph Convolutional Networks,GCN)提出于2017年。GCN 的出现标志着图神经网络的出现。深度学习…...
CANoe TC8测试脚本的结构介绍
CANoe TC8脚本是通过vTESTstudio平台编写。每个协议(ARP\ICMPv4\IPv4\UDP\TCP\SOMEIP\DHCP)都有自己的vtt文件。每个vtt文件的测试树结构为: Test Fixture Fixture Preparation Test Case Test Case … Test Case Test Case Fixture Completion 当Test Fixture里的Test Case…...
Python:操作 Excel 折叠
💖亲爱的技术爱好者们,热烈欢迎来到 Kant2048 的博客!我是 Thomas Kant,很开心能在CSDN上与你们相遇~💖 本博客的精华专栏: 【自动化测试】 【测试经验】 【人工智能】 【Python】 Python 操作 Excel 系列 读取单元格数据按行写入设置行高和列宽自动调整行高和列宽水平…...
【项目实战】通过多模态+LangGraph实现PPT生成助手
PPT自动生成系统 基于LangGraph的PPT自动生成系统,可以将Markdown文档自动转换为PPT演示文稿。 功能特点 Markdown解析:自动解析Markdown文档结构PPT模板分析:分析PPT模板的布局和风格智能布局决策:匹配内容与合适的PPT布局自动…...
ETLCloud可能遇到的问题有哪些?常见坑位解析
数据集成平台ETLCloud,主要用于支持数据的抽取(Extract)、转换(Transform)和加载(Load)过程。提供了一个简洁直观的界面,以便用户可以在不同的数据源之间轻松地进行数据迁移和转换。…...
uniapp中使用aixos 报错
问题: 在uniapp中使用aixos,运行后报如下错误: AxiosError: There is no suitable adapter to dispatch the request since : - adapter xhr is not supported by the environment - adapter http is not available in the build 解决方案&…...
学习STC51单片机32(芯片为STC89C52RCRC)OLED显示屏2
每日一言 今天的每一份坚持,都是在为未来积攒底气。 案例:OLED显示一个A 这边观察到一个点,怎么雪花了就是都是乱七八糟的占满了屏幕。。 解释 : 如果代码里信号切换太快(比如 SDA 刚变,SCL 立刻变&#…...
OPenCV CUDA模块图像处理-----对图像执行 均值漂移滤波(Mean Shift Filtering)函数meanShiftFiltering()
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 算法描述 在 GPU 上对图像执行 均值漂移滤波(Mean Shift Filtering),用于图像分割或平滑处理。 该函数将输入图像中的…...
面向无人机海岸带生态系统监测的语义分割基准数据集
描述:海岸带生态系统的监测是维护生态平衡和可持续发展的重要任务。语义分割技术在遥感影像中的应用为海岸带生态系统的精准监测提供了有效手段。然而,目前该领域仍面临一个挑战,即缺乏公开的专门面向海岸带生态系统的语义分割基准数据集。受…...
虚拟电厂发展三大趋势:市场化、技术主导、车网互联
市场化:从政策驱动到多元盈利 政策全面赋能 2025年4月,国家发改委、能源局发布《关于加快推进虚拟电厂发展的指导意见》,首次明确虚拟电厂为“独立市场主体”,提出硬性目标:2027年全国调节能力≥2000万千瓦࿰…...
华为OD机考-机房布局
import java.util.*;public class DemoTest5 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseSystem.out.println(solve(in.nextLine()));}}priv…...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...
