【设计模式】Java 设计模式之代理模式(Proxy Pattern)
代理模式深入分析
一、概述
代理模式是一种为其他对象提供一种代理以控制对这个对象的访问的设计模式。在某些情况下,一个对象不适合或者不能直接访问另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
代理模式的主要目的是:增强目标对象的功能;控制对目标对象的访问;实现目标对象与客户端的解耦。
二、模式结构
代理模式主要涉及三个角色:
- 抽象主题(Subject)角色:声明了真实主题和代理主题的共同接口,这样任何使用真实主题的地方都可以使用代理主题。
- 真实主题(RealSubject)角色:定义了代理所代表的真实对象,是我们最终要引用的对象。
- 代理主题(ProxySubject)角色:代理主题角色内部含有对真实主题的引用,从而可以操作真实主题对象,同时代理主题角色提供与真实主题相同的接口以便在任何时候都能代替真实主题。此外,代理主题角色还可以在执行真实主题操作前后添加一些操作。
三、实现方式
代理模式通常有两种实现方式:静态代理和动态代理。
- 静态代理
静态代理是代理模式的最直接实现方式。代理类和被代理类实现相同的接口,代理类持有被代理类的实例,通过调用被代理类的方法来完成实际的功能。
// 抽象主题
public interface Subject {void request();
}// 真实主题
public class RealSubject implements Subject {@Overridepublic void request() {System.out.println("Called RealSubject request()");}
}// 代理主题
public class ProxySubject implements Subject {private RealSubject realSubject;public ProxySubject() {this.realSubject = new RealSubject();}@Overridepublic void request() {// 在调用真实主题前添加一些操作System.out.println("Before calling RealSubject request()");realSubject.request();// 在调用真实主题后添加一些操作System.out.println("After calling RealSubject request()");}
}
- 动态代理
动态代理利用了Java的反射机制,在运行时动态地生成代理类。这种方式比静态代理更加灵活,不需要为每个被代理类编写一个代理类。Java标准库中的java.lang.reflect.Proxy类和java.lang.invoke.MethodHandles.Lookup类是动态代理的关键。
// 抽象主题
public interface Subject {void request();
}// 真实主题
public class RealSubject implements Subject {@Overridepublic void request() {System.out.println("Called RealSubject request()");}
}// 动态代理工厂
public class ProxyFactory {@SuppressWarnings("unchecked")public static <T> T getProxyInstance(Class<T> interfaceClass, InvocationHandler handler) {return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(),new Class<?>[]{interfaceClass},handler);}
}// 调用处理器
public class MyInvocationHandler implements InvocationHandler {private Object target;public MyInvocationHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 在调用方法前添加一些操作System.out.println("Before method " + method.getName());Object result = method.invoke(target, args);// 在调用方法后添加一些操作System.out.println("After method " + method.getName());return result;}
}// 客户端代码
public class Client {public static void main(String[] args) {RealSubject realSubject = new RealSubject();InvocationHandler handler = new MyInvocationHandler(realSubject);Subject subject = ProxyFactory.getProxyInstance(Subject.class, handler);subject.request();}
}
四、优缺点分析
优点:
- 职责清晰:将一部分工作交给代理对象处理,真实对象可以更加专注于其核心功能的实现。
- 控制访问:代理模式可以对访问真实对象进行一定的限制和控制,比如权限校验、访问日志记录等。
- 高扩展性:可以在不修改原有代码的基础上增加新的功能。
缺点:
- 性能损耗:由于代理对象的存在,每次访问真实对象都需要通过代理对象,因此会有一定的性能损耗。
- 代码复杂度:使用代理模式会增加代码的复杂度,特别是在使用动态代理时,需要编写额外的代理工厂类和调用处理器类。
五、常见应用场景
- 远程代理:为一个对象在不同的地址空间提供局部代表,这样可以将网络细节隐藏起来。
- 虚拟代理:根据需要创建开销很大的对象。通过它来存放实例化需要很长时间的真实对象。
- 保护代理:控制对原始对象的访问权限。
- 智能引用:当调用真实对象时,代理对象进行一些附加操作,如计算真实对象的引用次数等。
六、应用案例解读
以远程代理为例,假设我们有一个远程服务,客户端需要调用这个服务上的方法。出于性能和安全考虑,我们不希望客户端直接调用远程服务,而是通过一个代理对象来进行调用。代理对象负责处理网络通信、序列化/反序列化等细节,而客户端只需与代理对象交互即可。
// 远程服务接口
public interface RemoteService {String callRemoteMethod(String arg);
}// 远程服务实现(位于远程服务器上)
public class RemoteServiceImpl implements RemoteService {@Overridepublic String callRemoteMethod(String arg) {// 执行一些远程服务上的操作return "Result from remote service: " + arg;}
}// 远程服务代理
public class RemoteServiceProxy implements RemoteService {private String serverAddress;public RemoteServiceProxy(String serverAddress) {this.serverAddress = serverAddress;}@Overridepublic String callRemoteMethod(String arg) {// 处理网络通信,发送请求到远程服务器// 接收响应,并返回结果// 这里的实现会依赖于具体的网络通信库或框架String result = sendRequestToServer(serverAddress, arg);return result;}private String sendRequestToServer(String serverAddress, String arg) {// 简化示例,实际实现会涉及网络通信和序列化/反序列化return "Proxy result for server at " + serverAddress + ": " + arg;}
}// 客户端代码
public class Client {public static void main(String[] args) {RemoteService proxy = new RemoteServiceProxy("remote.server.address");String result = proxy.callRemoteMethod("Hello, remote service!");System.out.println(result);}
}
在这个案例中,RemoteServiceProxy作为远程服务的代理,负责处理与远程服务器的通信。客户端代码通过调用代理对象的callRemoteMethod方法,间接地调用远程服务上的方法。代理对象隐藏了网络通信的复杂性,使客户端代码更加简洁和易于管理。
总结来说,代理模式是一种强大的设计模式,它可以在不修改原有代码的基础上增强对象的功能、控制对象的访问以及实现解耦。通过合理地应用代理模式,我们可以构建出更加灵活、可扩展和易于维护的软件系统。
七、代理模式的变种
代理模式有多种变种,每一种都有其特定的用途和适用场景。
-
保护代理(Protection Proxy):控制对原始对象的访问权限。这种代理模式常用于实现访问控制,比如限制对某个方法的访问,或者检查用户权限等。
-
虚拟代理(Virtual Proxy):主要用于控制对开销大的对象的访问。当实际对象在创建或初始化时开销很大时,虚拟代理可以延迟对象的创建,直到真正需要时才创建。
-
智能引用代理(Smart Reference Proxy):当调用真实对象时,代理对象进行一些附加操作,比如引用计数、日志记录、事务处理等。
-
缓存代理(Caching Proxy):为开销大的运算结果提供暂时的存储,在下次运算时,如果输入相同则直接返回缓存的结果。
-
同步代理(Synchronization Proxy):为多个线程共享的资源提供安全访问。
八、与其他设计模式的关联
代理模式经常与其他设计模式结合使用,以实现更复杂的系统结构和功能。
-
与装饰器模式(Decorator Pattern):装饰器模式用于动态地给一个对象添加一些额外的职责。在某种意义上,代理模式也可以看作是一种特殊的装饰器模式,它主要关注于控制对对象的访问和增强对象的功能,而不是动态地添加职责。
-
与适配器模式(Adapter Pattern):适配器模式用于将一个类的接口转换成客户端所期望的另一种接口。在某些情况下,代理模式可以用来实现适配器模式,特别是当需要转换的接口涉及访问控制或延迟加载时。
九、最佳实践
-
明确代理的目的:在使用代理模式之前,首先要明确代理的目的,是为了增强功能、控制访问还是其他目的。
-
考虑性能开销:代理模式会增加一定的性能开销,因为每次访问原始对象都需要通过代理对象。因此,在性能敏感的场景中,需要仔细权衡是否使用代理模式。
-
保持接口一致性:代理对象应该尽量保持与原始对象相同的接口,这样客户端代码才能无缝地切换到代理对象。
-
谨慎使用动态代理:动态代理虽然提供了很大的灵活性,但也会增加代码的复杂性和维护成本。因此,在不需要动态代理的情况下,应尽量使用静态代理。
十、代理模式在实际项目中的应用
代理模式在实际项目中有着广泛的应用,特别是在需要增强对象功能、控制访问权限或实现延迟加载等场景中。以下是一些具体的应用示例:
-
数据库访问代理:在数据库访问层,我们通常会使用代理模式来封装数据库操作,实现连接池管理、事务控制、SQL日志记录等功能。代理对象负责处理与数据库的通信细节,而业务代码只需与代理对象交互,无需关心底层数据库的具体实现。
-
远程服务调用代理:在分布式系统中,服务之间的调用通常通过代理对象来实现。代理对象负责处理网络通信、序列化/反序列化、负载均衡等细节,使得服务调用更加简单和可靠。
-
文件访问代理:在文件系统中,我们可以使用代理模式来封装文件读写操作,实现文件加密、压缩、缓存等功能。代理对象负责处理文件的读写细节,而应用程序只需通过代理对象来访问文件。
-
API网关:在微服务架构中,API网关通常作为服务调用的代理,负责处理认证、授权、限流、熔断等逻辑。API网关作为代理对象,将客户端的请求转发到相应的微服务,并处理响应结果。
-
UI组件代理:在图形用户界面(GUI)开发中,代理模式可以用于封装复杂的UI组件,实现组件的延迟加载、动态替换或增强功能。代理对象可以管理组件的生命周期,并提供统一的接口供应用程序使用。
十一、代理模式的扩展与思考
代理模式作为一种经典的设计模式,在实际应用中有着广泛的应用场景。然而,随着技术的不断发展和业务需求的不断变化,我们也需要对代理模式进行扩展和思考。
-
异步代理:在处理耗时操作时,我们可以考虑使用异步代理来避免阻塞主线程。异步代理可以将操作放到后台线程中执行,并通过回调函数或Future对象等方式将结果返回给调用方。
-
动态代理与AOP(面向切面编程):动态代理是实现AOP的关键技术之一。通过动态代理,我们可以在不修改原有代码的情况下,为对象添加额外的行为(如日志记录、性能监控等)。这使得AOP成为一种强大的编程范式,能够极大地提高代码的可维护性和可扩展性。
-
代理模式的性能优化:虽然代理模式带来了很多好处,但也会引入一定的性能开销。因此,在实际应用中,我们需要对代理模式进行性能优化。例如,我们可以通过缓存代理对象、减少代理链的长度、优化网络通信等方式来降低性能开销。
-
代理模式与其他技术的结合:随着技术的发展,出现了很多新的编程范式和技术,如函数式编程、响应式编程等。我们可以思考如何将代理模式与这些新技术结合,以更好地满足业务需求和提高代码质量。
十二、总结与展望
代理模式作为一种经典的设计模式,在软件开发中发挥着重要的作用。通过合理地应用代理模式,我们可以实现对象的增强、访问控制、延迟加载等功能,提高代码的可维护性和可扩展性。然而,随着技术的不断发展和业务需求的不断变化,我们也需要对代理模式进行扩展和思考,以适应新的挑战和需求。未来,我们可以继续探索代理模式与其他技术的结合,以及如何更好地优化代理模式的性能,为构建高质量的软件系统提供有力支持。
相关文章:
【设计模式】Java 设计模式之代理模式(Proxy Pattern)
代理模式深入分析 一、概述 代理模式是一种为其他对象提供一种代理以控制对这个对象的访问的设计模式。在某些情况下,一个对象不适合或者不能直接访问另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。 代理模式的主要目的是…...
逻辑数据平台的 NoETL 之道(内含QA)
作者简介: 余俊,Aloudata 合伙人 & 技术副总裁。拥有 18 年互联网技术和大数据平台相关架构经验。作为主架构师及核心研发主导并完成了 Alibaba B2B 首个海量分布式 KV 存储系统,作为网站架构师负责 Aliexpress 全球买全球卖交易系统的第…...
低代码与数智制造:引领软件开发的革新之旅
在当今快速发展的数字化时代,软件开发已经渗透到各行各业,成为推动社会进步的重要力量。随着技术的不断进步,低代码开发与数智制造正逐渐崭露头角,成为引领软件开发领域革新的两大关键要素。本文将深入探讨低代码与数智制造的内涵…...
安装 AWS Load Balancer Controller 附加组件
1 创建一个 IAM policy #curl -O https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.4.4/docs/install/iam_policy.json#aws iam create-policy \--policy-name AWSLoadBalancerControllerIAMPolicy \--policy-document file://iam_policy.…...
性能测试什么时候开始?性能测试流程介绍
性能测试什么时候开始? 一般在系统功能稳定没有大的缺陷之后开始执行。但前期准备工作可以从系统需求分析时就开始:性能目标制定、场景获取、环境申请等。 一、制定性能测试目标 在特定的并发用户数下测试特定场景的响应时间 在一定的响应时间的要求下来测试特…...
爬虫逆向实战(36)-某建设监管平台(RSA,魔改)
一、数据接口分析 主页地址:某建设监管平台 1、抓包 通过抓包可以发现网站首先是请求了一个/prod-api/mohurd-pub/vcode/genVcode的接口,用于获取滑块验证码的图片 滑块验证之后,请求了/prod-api/mohurd-pub/dataServ/findBaseEntDpPage这…...
DeepLearning in Pytorch|共享单车预测NN详解(思路+代码剖析)
目录 概要 一、代码概览 二、详解 基本逻辑 1.数据准备 2.设计神经网络 初版 改进版 测试 总结 概要 原文链接:DeepLearning in Pytorch|我的第一个NN-共享单车预测 我的第一个深度学习神经网络模型---利用Pytorch设计人工神经网络对某地区租赁单车的使用…...
如何配置Apache的反向代理
目录 前言 一、反向代理的工作原理 二、Apache反向代理的配置 1. 安装Apache和相关模块 2. 配置反向代理规则 3. 重启Apache服务器 三、常见的使用案例 1. 负载均衡 2. 缓存 3. SSL加密 总结 前言 随着Web应用程序的不断发展和扩展,需要处理大量的请求和…...
Vue.js 应用实现监控可观测性最佳实践
前言 Vue 是一款用于构建用户界面的 JavaScript 框架。它基于标准 HTML、CSS 和 JavaScript 构建,并提供了一套声明式的、组件化的编程模型,帮助你高效地开发用户界面。无论是简单还是复杂的界面,Vue 都可以胜任。 TinyPro 是一套使用 Vue …...
Rust 语言中符号 :: 的使用场景
在 Rust 语言中,:: 符号主要用于以下几个场合: 指定关联函数或关联类型: 关联函数(也称为静态方法)是与类型关联而非实例关联的函数。它们使用 :: 符号来调用。例如: let value String::from("Hello,…...
Java 获取笔记本WiFi网络基站信息的方法
在Android开发中,获取基站信息(如基站ID、运营商信息、信号强度等)通常涉及使用TelephonyManager类。请注意,由于隐私和安全的考虑,从Android 10(API级别29)开始,对访问此类信息的权…...
Python如何处理拥塞控制
拥塞控制是计算机网络中用于防止网络拥塞(即过多的数据导致网络性能下降)的一系列技术和算法。在Python中,处理拥塞控制通常不直接涉及到代码层面的实现,因为拥塞控制主要是在网络协议栈(如TCP/IP)和操作系…...
【ArcGIS】栅格数据进行标准化(归一化)处理
栅格数据进行标准化(归一化)处理 方法1:栅格计算器方法2:模糊分析参考 栅格数据进行标准化(归一化)处理 方法1:栅格计算器 栅格计算器(Raster Calculator) 计算完毕后,得到归一化…...
【CMake】顶层 CMakeList.txt 常用命令总结
文章目录 cmake_minimum_required简介使用案例普通设置执行构建的cmake版本低于<min> project简介使用案例普通设置 set简介使用案例普通设置 cmake_minimum_required 简介 功能:为项目设置cmake的最低要求版本常用程度:⭐⭐⭐⭐⭐命令格式 cma…...
mac启动elasticsearch
1.首先下载软件,然后双击解压,我用的是7.17.3的版本 2.然后执行如下命令 Last login: Thu Mar 14 23:14:44 on ttys001 diannao1xiejiandeMacBook-Air ~ % cd /Users/xiejian/local/software/elasticsearch/elasticsearch-7.17.3 diannao1xiejiandeMac…...
【FFmpeg】ffmpeg 命令行参数 ⑤ ( 使用 ffmpeg 命令提取 音视频 数据 | 保留封装格式 | 保留编码格式 | 重新编码 )
文章目录 一、使用 ffmpeg 命令提取 音视频 数据1、提取音频数据 - 保留封装格式2、提取视频数据 - 保留封装格式3、提取视频数据 - 保留编码格式4、提取视频数据 - 重新编码5、提取音频数据 - 保留编码格式6、提取音频数据 - 重新编码 一、使用 ffmpeg 命令提取 音视频 数据 1…...
JMeter 二次开发之环境准备
通过JMeter二次开发,可以充分发挥JMeter的潜力,定制化和扩展工具的能力以满足具体需求。无论是开发自定义插件、函数二次开发还是定制UI,深入学习和掌握JMeter的二次开发技术,将为接口功能测试/接口性能测试工作带来更多的便利和效…...
Laravel Class ‘Facade\Ignition\IgnitionServiceProvider‘ not found 解决
Laravel Class Facade\Ignition\IgnitionServiceProvider not found 问题解决 问题 在使用laravel 更新本地依赖环境时,出现报错,如下: 解决 这时候需要更新本地的composer,然后在更新本地依赖环境。 命令如下: co…...
DNS 技巧与窍门
简介 在本文中,您将学习三种可以使用 DNS 完成的技巧。如果您曾经进行过任何与 DNS 配置相关的工作,这些小技巧可能会帮助您更快地完成工作流程。您将学习一些在终端中使用的命令和处理 DNS 数据的方法,比如如何检查当前的域名服务器。完成后…...
第2章 信息技术基础
本章学习要点 全面了解医院信息系统建设所涉及的主要信息技术以及这些技术的应用情况。 计算机与网络、信息技术与信息系统、数字媒体与数据存储技术、条形码(二维码)、RFID技术、云计算、APP技术 1.XML 可扩展标记语言与Access,Oracle和SQL Server等数据库不同…...
浏览器访问 AWS ECS 上部署的 Docker 容器(监听 80 端口)
✅ 一、ECS 服务配置 Dockerfile 确保监听 80 端口 EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]或 EXPOSE 80 CMD ["python3", "-m", "http.server", "80"]任务定义(Task Definition&…...
从WWDC看苹果产品发展的规律
WWDC 是苹果公司一年一度面向全球开发者的盛会,其主题演讲展现了苹果在产品设计、技术路线、用户体验和生态系统构建上的核心理念与演进脉络。我们借助 ChatGPT Deep Research 工具,对过去十年 WWDC 主题演讲内容进行了系统化分析,形成了这份…...
.Net框架,除了EF还有很多很多......
文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...
【入坑系列】TiDB 强制索引在不同库下不生效问题
文章目录 背景SQL 优化情况线上SQL运行情况分析怀疑1:执行计划绑定问题?尝试:SHOW WARNINGS 查看警告探索 TiDB 的 USE_INDEX 写法Hint 不生效问题排查解决参考背景 项目中使用 TiDB 数据库,并对 SQL 进行优化了,添加了强制索引。 UAT 环境已经生效,但 PROD 环境强制索…...
解锁数据库简洁之道:FastAPI与SQLModel实战指南
在构建现代Web应用程序时,与数据库的交互无疑是核心环节。虽然传统的数据库操作方式(如直接编写SQL语句与psycopg2交互)赋予了我们精细的控制权,但在面对日益复杂的业务逻辑和快速迭代的需求时,这种方式的开发效率和可…...
DIY|Mac 搭建 ESP-IDF 开发环境及编译小智 AI
前一阵子在百度 AI 开发者大会上,看到基于小智 AI DIY 玩具的演示,感觉有点意思,想着自己也来试试。 如果只是想烧录现成的固件,乐鑫官方除了提供了 Windows 版本的 Flash 下载工具 之外,还提供了基于网页版的 ESP LA…...
解决本地部署 SmolVLM2 大语言模型运行 flash-attn 报错
出现的问题 安装 flash-attn 会一直卡在 build 那一步或者运行报错 解决办法 是因为你安装的 flash-attn 版本没有对应上,所以报错,到 https://github.com/Dao-AILab/flash-attention/releases 下载对应版本,cu、torch、cp 的版本一定要对…...
深入解析C++中的extern关键字:跨文件共享变量与函数的终极指南
🚀 C extern 关键字深度解析:跨文件编程的终极指南 📅 更新时间:2025年6月5日 🏷️ 标签:C | extern关键字 | 多文件编程 | 链接与声明 | 现代C 文章目录 前言🔥一、extern 是什么?&…...
C++使用 new 来创建动态数组
问题: 不能使用变量定义数组大小 原因: 这是因为数组在内存中是连续存储的,编译器需要在编译阶段就确定数组的大小,以便正确地分配内存空间。如果允许使用变量来定义数组的大小,那么编译器就无法在编译时确定数组的大…...
人工智能(大型语言模型 LLMs)对不同学科的影响以及由此产生的新学习方式
今天是关于AI如何在教学中增强学生的学习体验,我把重要信息标红了。人文学科的价值被低估了 ⬇️ 转型与必要性 人工智能正在深刻地改变教育,这并非炒作,而是已经发生的巨大变革。教育机构和教育者不能忽视它,试图简单地禁止学生使…...
