设计模式-代理模式
控制和管理访问
玩过扮白脸,扮黑脸的游戏吗?你是一个白脸,提供很好且很友善的服务,但是你不希望每个人都叫你做事,所以找了黑脸控制对你的访问。这就是代理要做的:控制和管理对象。
监视器编码
需求:糖果机能够获得更好的监控,需要提供一份库存以及机器状态的报告。
先为GumballMachine加上处理位置的支持:
public class GumballMachine{//位置用String记录String location;//位置被传入构造器中,然后存到实例变量中public GumballMachine(String location,int count){//构造器内的其他代码this.location = location;}//getter方法,获取位置信息public String getLocation(){return location;}}
创建另一个类,GumballMonitor(糖果监视器),以便取得机器的位置、糖果的库存量以及当前机器的状态,并打印成一份报告。
public class GumballMonitor{GumballMachine machine;public GumballMonitor(GumballMachine machine){this.machine = machine;}//打印报告方法,将位置,库存,机器状态打印出来public void report(){System.out.println("Gumball Machine: "+machine.getLocation());System.out.println("Current inventory: "+machine.getCount() +" gumballs");System.out.println("Current state: "+machine.getState());}
}
远程代理
远程代理就好比“远程对象的本地代表”。
何谓“远程对象”?这是一种对象,活在不同的java虚拟机(JVM)堆中。可以理解为在不同的地址空间运行的远程对象。
何谓“本地代表”?这是一种可以由本地方法调用的对象,其行为会转发到远程对象中。

你的客户对象所做的就像是在做远程方法调用,但其实只是调用本地堆中的“代理”对象上的方法,再由代理处理所有网络通信的底层细节。
远程代理应用
如何创建一个代理,知道如何调用在另一个JVM中的对象的方法?
我们无法取得另一个堆的对象的引用,不能这样写:
Duck d = <另一个堆的对象>
RMI可以让我们找到远程JVM内的对象,并允许我们调用它们的方法。
关于RMI调用和本地(正常)的方法调用,有一个不同点。虽然调用远程方法就如同调用本地方法一样,但是客户辅助对象会通过网络发送方法调用,所以网络和I/O的确是存在的。

定义代理模式
远程代理是一般代理模式的一种实现,这个模式的变体很多。
代理模式为另一个对象提供一个替身或占位符以控制对这个对象的访问。
使用代理模式创建代表(representative)对象,让代表对象控制某对象的访问,被代理的对象可以是远程的对象、创建开销大的对象或需要安全控制的对象。
在糖果机的例子中,代理控制了对远程对象的访问。代理之所以需要控制访问,是因为我们的客户(监视器)不知道如何和远程对象沟通。从某个方面来看,远程代理控制访问,好帮我们处理网络上的细节。
代理模式有许多变体,而这些变体几乎都和“控制访问”的做法有关:
- 远程代理控制访问远程对象
- 虚拟代理控制访问创建开销大的资源
- 保护代理基于权限控制对资源的访问

代理模式总结
代理模式与装饰者模式的区别是什么?
装饰者模式为对象增加行为,而代理是控制对象的访问
代理模式与适配器模式的区别是什么?
代理和适配器都是挡在其他对象的前面,并负责将请求转发给它们。适配器会改变对象适配的接口,而代理则实现相同的接口
如何让客户使用代理,而不是真正的对象?
常用的技巧是提供一个工厂,实例化并返回主题。因为这是在工厂方法内发生的,我们可以用代理包装主题再返回,而客户不知道也不在乎他使用的是代理还是真东西
保护代理应用
Java在java.lang.reflect包中由自己的代理支持,利用这个包你可以在运行时动态地创建一个代理类,实现一个或多个接口,并将方法的调用转发到你所指定的类。
因为实际的代理类是在运行时创建的,我们称这个java技术为:动态代理
我们要利用Java的动态代理创建我们下一个代理实现(保护代理)。但在这之前,先让我们看一下类图,了解下动态代理是怎么一回事,就和真实世界中大多数的事物一样,它和代理模式的传统定义有一点出入。

上图所示,因为java已经为你创建了Proxy类,所以你需要有办法来告诉Proxy类你要做什么。你不能像以前一样把代码放在Proxy类中,因为Proxy不是你直接实现的。既然这样的代码不能放在Proxy类中,那么要放在哪里?放在InvocationHandler中。InvacationHandler的工作是响应代理的任何调用。你可以把InvacationHandler想成是代理收到方法调用后,请求做实际工作的对象。
对象村的配对
对乡村实现约会服务系统,每个城镇都配对服务,服务系统涉及一个Person bean,允许设置或取得一个人的信息:
public interface PersonBean{String getName();String getGender();String getInterests();int getHotOrNotRating();void setName(String name);void setGender(String gender);void setInterests(String interests);//需要一个整数作为参数,并将它加入此人的运行平均值中void setHotOrNotRating(int rating);
}
public class PersonBeanImpl implements PersonBean{String name;String gender;String interests;int rating;int ratingCount = 0;public String getName(){return name;}public String getGender(){return gender;}public int getHotOrNotRating(){if(ratingCount == 0){return 0;}return rating/ratingCount;}public void setName(String name){this.name = name;}public void setGender(String gender){this.gender = gender;}public void setInterests(String interests){this.interests = interests;}public void setHotOrNotRating(int rating){this.rating+=rating;ratingCoun++;}
}
系统不应该允许用户篡改别人的数据,根据我们定义PersonBean的方式,任何客户都可以调用任何方法。
这是一个我们可以使用保护代理的绝佳例子。什么事保护代理?这是一种根据访问权限决定客户可否访问对象的代理。比方说,如果你有一个雇员对象:
- 保护代理允许雇员调用对象上的某些方法
- 保护代理允许经理可以多调用一些其他的方法,像setSalary()
- 保护代理允许人力资源雇员调用对象上的所有方法
我们希望顾客可以设置自己的信息,同时又防止他人更改这些信息。HotOrNot评分则相反,你不能更改自己的评分,但是他人可以设置你的评分。
为PersonBean创建动态代理
顾客不可以改变自己的HotOrNot评分,也不可以改变其他顾客的个人信息。要修正这些问题,必须创建两个代理:
- 访问自己PersonBean对象
- 访问另一个顾客的PersonBean对象
步骤一:创建两个InvocationHandler
InvocationHandler实现了代理的行为,java负责创建真实代理类和对象。我们只需提供在方法调用发生时知道做什么的handler

这里只有一个名为invoke()的方法,不管代理被调用的是何种方法,处理器被调用的一定是invoke()方法。
- 假设proxy的setHotOrNotRating()方法被调用
proxy.setHotOrNotRating(9);
- proxy会接着调用invocationHandler的invoke()方法
//Method类是Reflection API的一部分,利用它的getName()方法,我们就可以知道proxy被调用的方法是什么
invoke(Object proxy,Method method,Object[] args)
- handler决定要如何处置这个请求,转发给RealSubject
//我们调用原始proxy被调用的方法。这个对象在调用时被传给我们,只不过加载调用的是真正的主题(person)
return method.invoke(person,args);
当proxy调用invoke()时,要如何应对?通常,会先检查该方法是否来自proxy,并基于该方法的名称和变量做决定。现在我们就来实现OwnerInvocationHandler,以了解工作机制:
import java.lang.reflect.*;//所有调用处理器都实现InvocationHandler接口
public class OwnerInvocationHandler implements InvocationHandler{PersonBean person;//我们将person传入构造器,并保持它的引用public OwnerInvocationHandler(PersonBean person){this.person = person;}//每次proxy的方法被调用,就会导致proxy调用此方法public Object invoke(Object proxy,Method method,Object[] args) throws IllegalAccessException{try{if(method.getName().startsWith("get")){//如果方法是一个getter,我们就调用person内的方法return method.invoke(person,args);}else if(method.getName.equals("setHotOrNotRating")){//如果是setHotOrNotRating()方法,我们就抛出IllegalAccessException表示不允许throw new IllegalAccessException();}else if(method.getName().startsWith("set")){//因为我们是拥有者,所以任何其他set方法都可以,我们就在真正主题上调用它return method.invoke(person,args);}}catch(InvocationTargetException e){//真正主题抛出异常的话,就会执行这里e.printStackTrace();}return null;}
}
步骤二:创建动态代理
创建动态Proxy类,并实例化Proxy对象。我们编写一个以PersonBean为参数,并知道如何为PersonBean对象创建拥有者代理的方法。也就是说,我们要创建一个代理,将它的方法调用转发给OwnerInvocationHandler。代码如下:
//此方法需要一个person对象作为参数,然后返回它的代理,因为代理和主题有相同的接口,所以我们返回一个PersonBean
PersonBean getOwnerProxy(PersonBean person){//利用Proxy类的静态newProxyInstance方法创建代理return (PersonBean)Proxy.newProxyInstance(person.getClass().getClassLoader(),//代理需要实现的接口person.getClass.getInterfaces(),new OwnerInvocationHandler(person));
}
步骤三:利用适当的代理包装任何PersonBean对象
来看下代理如何控制对setter方法的访问
public class MatchMakingTestDrive{public MatchMakingTestDrive(){initializeDatabase();}public void drive(){//从数据库中取出一个人PersonBean joe = getPersonFromDatabase("Joe");PersonBean ownerProxy = getOwnerProxy(joe);ownerProxy.setInterests("bowling,Go");try{ownerProxy.setHotOrNotRating(10);}catch(Exception e){System.out.println("Can't set rating from owner proxy");}PersonBean nonOwnerProxy = getNonOwnerProxy(joe);//非拥有者代理nonOwnerProxy.setHotOrNotRating(10);try{nonOwnerProxy.setInterests("bowling,Go");}catch(Exception e){System.out.println("Can't set interests from non owner proxy");}}
}
总结
- “动态代理”动态在哪里?是不是指在运行时才将它实例化并和handler联系起来?
不是的。动态代理之所以被称为动态,是因为运行时才将它的类创建出来。代码开始执行时,还没有proxy类,它是根据需要从你传入的接口集创建的。
- InvocationHandler看起来像一个很奇怪的proxy。它没有实现所代理的类的任何方法。
这是因为InvocationHandler根本就不是proxy,它只是一个帮助proxy的类,proxy会把调用转发给它处理。Proxy本身是利用静态的Proxy.newProxyInstance()方法在运行时动态地创建的。
- 有没有办法知道某个类是不是代理类呢?
可以。代理类有一个静态方法,叫做isProxyClass()。此方法的返回值如果为true,表示这是一个动态代理类。除此之外,代理类还会实现特定的某些接口。
- 代理模式
- 包装另一个对象,并控制对它的访问
- 适配器模式
- 包装另一个对象,并提供不同的接口
- 装饰者模式
- 包装另一个对象,并提供额外的行为
- 外观模式
- 包装许多对象以简化它们的接口
相关文章:
设计模式-代理模式
控制和管理访问 玩过扮白脸,扮黑脸的游戏吗?你是一个白脸,提供很好且很友善的服务,但是你不希望每个人都叫你做事,所以找了黑脸控制对你的访问。这就是代理要做的:控制和管理对象。 监视器编码 需求&…...
DPDK — MALLOC(librte_malloc,Memory Manager,内存管理组件)
目录 文章目录 目录MALLOC(librte_malloc,Memory Manager,内存管理组件)rte_malloc() 接口malloc_heap 结构体malloc_elem 结构体内存初始化流程内存申请流程内存释放流程MALLOC(librte_malloc,Memory Manager,内存管理组件) MALLOC 库基于 hugetlbfs 内核文件系统来实…...
【Java开发】Spring 12 :Spring IOC控制反转和依赖注入(解决单接口多实现类调用)
IOC 是 Inversion of Control 的简写,译为“控制反转”,Spring 通过 IOC 容器来管理所有 Java 对象的实例化和初始化,控制对象与对象之间的依赖关系。我们将由 IOC 容器管理的 Java 对象称为 Spring Bean,它与使用关键字 new 创建…...
【C++学习】基础语法(三)
众所周知C语言是面向过程的编程语言,关注的是过程;解决问题前,需要分析求解的步骤,然后编辑函数逐步解决问题。C是基于面向对象的,关注的是对象,将一件事拆分成不同的对象,不同对象间交互解决问…...
k8s自动化安装脚本(kubeadm-1.23.7)
文章目录介绍软件架构版本介绍更新内容2023-02-192023-02-152023-02-142023-02-102022-10-202022-08-06准备部署包操作步骤环境准备结构备注解压部署包修改host文件脚本使用方式初始化环境验证ansible配置安装k8s集群登录master的节点添加node节点master节点状态检查组件安装安…...
面试题记录
Set与Map的区别 map是键值对,set是值的集合。键,值可以是任何类型map可以通过get获取,map不能。都能通过迭代器进行for…of遍历set的值是唯一的,可以做数组去重,map,没有格式限制,可以存储数据…...
链式前向星介绍以及原理
1 链式前向星 1.1 简介 链式前向星可用于存储图,本质上是一个静态链表。 一般来说,存储图常见的两种方式为: 邻接矩阵邻接表 邻接表的实现一般使用数组实现,而链式前向星就是使用链表实现的邻接表。 1.2 出处 出处可参考此…...
jenkins 安装 -适用于在线安装 后续写个离线安装的
jenkins安装1.下载jenkins2.安装启动3.附件卸载jdk的命令4.配置jenkins一、在jenkins配置文件中配置jdk环境变量二、修改jenkins默认的操作用户1.下载jenkins jenkins官网下载 https://www.jenkins.io/ 点击下载 我是centos系统所以选择centos,点击后按着官方提供…...
【C++】再谈vscode界面调试C++程序(linux) - 知识点目录
再谈vscode界面调试C程序(linux) 配套文档:vscode界面调试C程序(linux) 命令解释 g -g ../main.cpp 编译main.cpp文件; -g:生成调试信息。编译器会在可执行文件中嵌入符号表和源代码文件名&…...
蚂蚁感冒---第五届蓝桥杯真题
目录 题目链接 题目描述 分析: 代码: y总综合 666 题目链接 1211. 蚂蚁感冒 - AcWing题库 题目描述 分析: y总真牛逼,掉头等价于穿过,以第一个点为分界点,分别判断 代码: (自…...
常见排序算法--Java实现
常见排序算法--Java实现插入排序直接插入排序折半插入排序希尔排序交换排序冒泡排序快速排序选择排序直接选择排序堆排序归并排序基数排序各种排序方法比较在网上找了些排序算法的资料。此篇笔记本人总结比较,简单注释,觉得比较好理解,且相对…...
算法笔记(九)—— 暴力递归
暴力递归(尝试) 1. 将问题转化为规模缩小了的同类问题子问题 2. 有明确的不需要的继续递归的条件 3. 有当得到子问题结果之后的决策过程 4. 不记录每一个子问题的解 Question:经典汉诺塔问题 1. 理解清楚,基础三个圆盘的移动…...
Flask框架学习记录
Flask项目简要 项目大致结构 flaskDemo1 ├─static ├─templates └─app.py app.py # 从flask这个包中导入Flask类 from flask import Flask# 使用Flask类创建一个app对象 # __name__:代表当前app.py这个模块 # 1.以后出现bug,可以帮助快速定位 # 2.对于寻找…...
【Opencv 系列】 第6章 人脸检测(Haar/dlib) 关键点检测
本章内容 1.人脸检测,分别用Haar 和 dlib 目标:确定图片中人脸的位置,并画出矩形框 Haar Cascade 哈尔级联 核心原理 (1)使用Haar-like特征做检测 (2)Integral Image : 积分图加速特征计算 …...
信源分类及数学模型
本专栏包含信息论与编码的核心知识,按知识点组织,可作为教学或学习的参考。markdown版本已归档至【Github仓库:information-theory】,需要的朋友们自取。或者公众号【AIShareLab】回复 信息论 也可获取。 文章目录信源分类按照信源…...
Games101-202作业1
一. 将模型从模型空间变换到世界空间下 在这个作业下,我们主要进行旋转的变换。 二.视图变换 ,将相机移动到坐标原点,同时保证物体和相机进行同样的变换(这样对形成的图像没有影响) 在这个作业下我们主要进行摄像机的平移变换&am…...
Linux系统之终端管理命令的基本使用
Linux系统之终端管理命令的基本使用一、检查本地系统环境1.检查系统版本2.检查系统内核版本二、终端介绍1.终端简介2.Linux终端简介3.终端的发展三、终端的相关术语1.终端模拟器2.tty终端3.pts终端4.pty终端5.控制台终端四、终端管理命令ps1.直接使用ps命令2.列出登录详细信息五…...
【Mongoose笔记】MQTT 服务器
【Mongoose笔记】MQTT 服务器 简介 Mongoose 笔记系列用于记录学习 Mongoose 的一些内容。 Mongoose 是一个 C/C 的网络库。它为 TCP、UDP、HTTP、WebSocket、MQTT 实现了事件驱动的、非阻塞的 API。 项目地址: https://github.com/cesanta/mongoose学习 下面…...
数据结构概述
逻辑结构 顺序存储 随机访问是可以通过下标取到任意一个元素,即数组的起始位置下标 链式存储 链式存储是不连续的,比如A只保留了当前的指针,那么怎么访问到B和C呢 每个元素不仅存储自己的值还使用额外的空间存储指针指向下一个元素的地址&a…...
【前端】Vue3+Vant4项目:旅游App-项目总结与预览(已开源)
文章目录项目预览首页Home日历:日期选择开始搜索位置选择上搜索框热门精选-房屋详情1热门精选-房屋详情2其他页面项目笔记项目代码项目数据项目预览 启动项目: npm run dev在浏览器中F12: 首页Home 热门精选滑动到底部后会自动加载新数据&a…...
ardupilot 开发环境eclipse 中import 缺少C++
目录 文章目录 目录摘要1.修复过程摘要 本节主要解决ardupilot 开发环境eclipse 中import 缺少C++,无法导入ardupilot代码,会引起查看不方便的问题。如下图所示 1.修复过程 0.安装ubuntu 软件中自带的eclipse 1.打开eclipse—Help—install new software 2.在 Work with中…...
JVM暂停(Stop-The-World,STW)的原因分类及对应排查方案
JVM暂停(Stop-The-World,STW)的完整原因分类及对应排查方案,结合JVM运行机制和常见故障场景整理而成: 一、GC相关暂停 1. 安全点(Safepoint)阻塞 现象:JVM暂停但无GC日志,日志显示No GCs detected。原因:JVM等待所有线程进入安全点(如…...
计算机基础知识解析:从应用到架构的全面拆解
目录 前言 1、 计算机的应用领域:无处不在的数字助手 2、 计算机的进化史:从算盘到量子计算 3、计算机的分类:不止 “台式机和笔记本” 4、计算机的组件:硬件与软件的协同 4.1 硬件:五大核心部件 4.2 软件&#…...
相关类相关的可视化图像总结
目录 一、散点图 二、气泡图 三、相关图 四、热力图 五、二维密度图 六、多模态二维密度图 七、雷达图 八、桑基图 九、总结 一、散点图 特点 通过点的位置展示两个连续变量之间的关系,可直观判断线性相关、非线性相关或无相关关系,点的分布密…...
零基础在实践中学习网络安全-皮卡丘靶场(第十一期-目录遍历模块)
经过前面几期的内容我们学习了很多网络安全的知识,而这期内容就涉及到了前面的第六期-RCE模块,第七期-File inclusion模块,第八期-Unsafe Filedownload模块。 什么是"遍历"呢:对学过一些开发语言的朋友来说应该知道&…...
WinUI3开发_使用mica效果
简介 Mica(云母)是Windows10/11上的一种现代化效果,是Windows10/11上所使用的Fluent Design(设计语言)里的一个效果,Windows10/11上所使用的Fluent Design皆旨在于打造一个人类、通用和真正感觉与 Windows 一样的设计。 WinUI3就是Windows10/11上的一个…...
自动化立体仓库堆垛机控制系统STEP7 OB1功能块
1、堆垛机控制系统STEP7硬件组态如下图 CPU CPU 314C-2 PN/DP 6ES7 314-6EH04-0AB0 SM 338 POS-INPUT AO2x12Bit 6ES7 332-5HB01-0AB0 2、堆垛机控制系统STEP7内部变量 前进HMI M 0.0 BOOL 后退HMI M 0.1 BOOL 上升HMI M 0.2 B…...
【Oracle】数据仓库
个人主页:Guiat 归属专栏:Oracle 文章目录 1. 数据仓库概述1.1 为什么需要数据仓库1.2 Oracle数据仓库架构1.3 Oracle数据仓库关键技术 2. 数据仓库建模2.1 维度建模基础2.2 星形模式设计2.3 雪花模式设计2.4 缓慢变化维度(SCD)处…...
AI短视频创富营
课程内容: 相关资料 【第一章】前期准备 001.【涨粉技巧】新账号如何快速涨粉?_ev(1).mp4 002.【带贷权限】如何开通账号带贷权限?(1).mp4 003.【费用缴纳】如何缴纳账号保证金?_ev(1).mp4 004.【账号检测】如何检测账号是否限流?(1).mp4 005.【风险规避…...
C#和C++在编译过程中的文件区分
1. .h是头文件(Header File) 用来 声明类、函数、常量等。 通常不包含实际实现,只是“定义接口” // 示例:math_utils.h#pragma once int add(int a, int b); //定义函数名2. .cpp是源文件(Source File&…...
