当前位置: 首页 > news >正文

Java设计模式---代理模式

引言

在软件设计领域,代理模式(Proxy Pattern)作为一种经典的结构型设计模式,其核心思想是在客户端与目标对象之间引入一个代理对象,以控制对目标对象的访问。这种模式不仅能够在不修改原有代码的基础上增强功能,还能有效解耦客户端与目标对象,提升系统的灵活性和可维护性。本文将从代理模式的基本概念出发,深入探讨其应用场景、不同类型(静态代理与动态代理)的实现方式,并通过具体的Java代码案例进行详细解析,最后总结其优缺点以及与装饰器模式的区别,旨在帮助读者全面理解并掌握代理模式的设计精髓与实践技巧。

代理模式UML类图(静态代理)

为了更好地理解静态代理的结构,下面通过Mermaid语法绘制了代理模式的UML类图,展示了客户端、代理类、抽象接口和真实类之间的关系:

classDiagram class Client { +main() } class Subject { <<interface>> +request() } class RealSubject { +request() } class Proxy { -realSubject: RealSubject +request() +preRequest() +postRequest() } Client --> Subject Subject <|.. RealSubject Subject <|.. Proxy Proxy --> RealSubject note for Proxy "代理类持有真实对象的引用,\n并在调用真实对象方法前后\n执行额外操作(如权限检查、\n日志记录、延迟初始化等)" note for Client "客户端通过代理对象访问服务,\n无需直接接触真实对象"

类图说明:

  • Client(客户端):程序的调用者,通过代理对象访问服务。
  • Subject(抽象接口):定义代理类和真实类共同实现的接口,确保两者具有相同的行为。
  • RealSubject(真实类):实际执行业务逻辑的对象,实现Subject接口。
  • Proxy(代理类):实现Subject接口,持有RealSubject的引用,在调用真实对象方法前后可以执行额外的操作(如权限验证、日志记录、缓存等)。

在静态代理中,代理关系在编译期就已经确定,代理类和真实类都实现相同的接口,代理类通过组合方式持有真实对象的引用,并在调用真实对象方法前后添加额外的控制逻辑。

介绍

代理模式中,在客户端与对象之间增加了一个代理层。客户端在进行访问时候,不是直接访问对象,而是访问代理。代理模式是一种结构型的设计模式。通过代理模式,可以解决直接访问对象带来的一些问题,并且可以进行访问控制。

代理模式的实现中,代理类中依赖实体类,但两者共同实现相同的接口。代理类中对应接口中,会调用实体类的对应接口。听上去与装饰器的实现一样,是的,两者的实现基本上是相同的,只是使用意义上的侧重点不同。两者的区别,会在文末的总结中进行分析。

一、代理模式的应用场景

代理模式的应用场景比较广泛,跟"代理"两个字沾边的,一般都会涉及代理模式。比如:

  • 火车票代售站:对于乘客来说,他不需要去真正的火车站,而通过代售点,就可以实现买票取票的功能。
  • VPN:由于众所周知的原因,我们不能访问youtube,那么我们可以设置一个VPN,通过VPN进行访问。
  • Windows的快捷方式:也是一种代理。
  • Spring的AOP:面向切面编程中大量使用了代理模式。

除了上述生活化的例子,代理模式在软件开发中还有多种技术实现,以下是几个典型的技术应用场景:

  • 远程代理(RPC框架):在分布式系统中,客户端需要调用远程服务时,远程代理充当本地代表。客户端调用本地代理对象的方法,代理负责网络通信、序列化/反序列化等细节,将请求转发给远程服务,并将结果返回给客户端。这样客户端无需关心网络通信的复杂性,就像调用本地方法一样简单。
  • 虚拟代理(图片懒加载):在Web开发中,当页面包含大量图片时,虚拟代理可以延迟图片的实际加载。代理对象先显示占位图或加载动画,当图片进入可视区域时,代理才真正加载原始图片资源。这能显著提升页面初始加载速度,优化用户体验。
  • 保护代理(权限控制):在需要访问控制的系统中,保护代理在客户端访问目标对象前进行权限验证。例如,用户管理系统中的代理可以检查用户角色和权限,只有具备相应权限的用户才能执行敏感操作,否则拒绝访问或返回错误信息。
  • 缓存代理:为昂贵的操作(如数据库查询、复杂计算)提供缓存功能。代理首先检查缓存中是否存在结果,如果存在则直接返回,否则调用真实对象执行操作并将结果缓存起来,后续相同请求可以直接使用缓存,提高系统性能。

二、代理模式的类型

代理模式从类型上来说分为两种:

  • 静态代理:在编译期间就确定的代理关系,一般是一对一的。
  • 动态代理:一对多的代理关系,是在运行时态才能确定的代理关系。

动态代理的原理比较复杂,本篇文章只简单介绍动态代理的使用,在下一篇文章中,会从原理和代码的层面,对动态代理做专门的解读。

三、静态代理案例

3.1 背景

以购买火车票为例。乘客(客户端)在购买火车票的时候,可以在代售点(代理类)进行购买(功能),而不需要花费很长的时间跑去火车站(真实类)进行购买(功能)。

3.2 实现

3.2.1 定义抽象接口
public interface Shop { void buy(); void take(); }
3.2.2 定义真实火车站
public class Station implements Shop { @Override public void buy() { System.out.println("购买火车票"); } @Override public void take() { System.out.println("领取火车票"); } }
3.2.3 定义代售点(代理类)
public class ProxyStation implements Shop { private Station station = new Station(); @Override public void buy() { beforeBuy(); station.buy(); afterBuy(); } private void beforeBuy() { System.out.println("购买之前操作"); } private void afterBuy() { System.out.println("购买之后的操作"); } @Override public void take() { beforeTake(); station.take(); afterTake(); } private void afterTake() { System.out.println("取票之前的操作"); } private void beforeTake() { System.out.println("取票之后的操作"); } }
3.2.4 验证程序
public class Test { public static void main(String args[]) { ProxyStation proxyStation = new ProxyStation(); proxyStation.buy(); proxyStation.take(); } }
3.2.5 运行结果

购买之前操作
购买火车票
购买之后的操作
取票之后的操作
领取火车票
取票之前的操作

Process finished with exit code 0

四、动态代理

动态代理的场景是一对多的关系,即是一个代理类,多个被代理的类。

在动态代理中,不再关注向客户端屏蔽原始对象(客户端可以看到原始对象),而重点是对于一系列的原始实现类,能够对抽象中的方法进行统一的横向扩展(方法执行前/后的操作),而不需要为每一个原始实现类都创建一个代理。

动态代理的原理,会在下篇文章中进行分析。

4.1 案例

以汽车(抽象)行驶为例子,我们要统计多种车型(原始实现类)通过某段距离的用时。在汽车行驶前,需要进行开始计时的操作(功能扩展)。汽车行驶后,要进行结束计时的操作(功能扩展)。

4.2 实现

4.2.1 抽象接口
public interface Moveable { void move() throws Exception; void move_back() throws Exception; }
4.2.2 定义实现类
import java.util.Random; public class Car implements Moveable { public void move() throws Exception { Thread.sleep(new Random().nextInt(1000)); System.out.println("轿车行驶中…"); } public void move_back() throws Exception { Thread.sleep(new Random().nextInt(1000)); System.out.println("轿车向后行驶中…"); } }
import java.util.Random; public class Truck implements Moveable { public void move() throws Exception { Thread.sleep(new Random().nextInt(1000)); System.out.println("卡车行驶中…"); } public void move_back() throws Exception { Thread.sleep(new Random().nextInt(1000)); System.out.println("卡车向后行驶中…"); } }
4.2.3 定义动态代理工具类

动态代理工具类需要实现InvocationHandler接口,实现invoke方法,通过反射实现对抽象接口方法的调用method.invoke(target, args);

import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class TimeHandler implements InvocationHandler { private Object target; public TimeHandler(Object target) { super(); this.target = target; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { long startTime = System.currentTimeMillis(); System.out.println("汽车开始行驶…"); method.invoke(target, args); long stopTime = System.currentTimeMillis(); System.out.println("汽车结束行驶…汽车行驶时间:" + (stopTime - startTime) + "毫秒!"); return null; } }
4.2.4 验证程序
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; public class Test { public static void main(String[] args) throws Exception { Car car = new Car(); Class cls = car.getClass(); InvocationHandler h = new TimeHandler(car); Moveable m = (Moveable) Proxy.newProxyInstance(cls.getClassLoader(), cls.getInterfaces(), h); m.move(); System.out.println(""); m.move_back(); System.out.println(""); System.out.println(""); Truck truck = new Truck(); cls = truck.getClass(); h = new TimeHandler(truck); m = (Moveable) Proxy.newProxyInstance(cls.getClassLoader(), cls.getInterfaces(), h); m.move(); System.out.println(""); m.move_back(); System.out.println(""); System.out.println(""); } }
4.2.5 运行结果

汽车开始行驶…
轿车行驶中…
汽车结束行驶…汽车行驶时间:200毫秒!

汽车开始行驶…
轿车向后行驶中…
汽车结束行驶…汽车行驶时间:681毫秒!

汽车开始行驶…
卡车行驶中…
汽车结束行驶…汽车行驶时间:492毫秒!

汽车开始行驶…
卡车向后行驶中…
汽车结束行驶…汽车行驶时间:338毫秒!

Process finished with exit code 0

五、总结

5.1 代理模式优缺点

代理类型优点缺点
静态代理
  • 具有较好的扩展性,可以在不修改目标对象的前提下增强功能
  • 能够屏蔽原始实现类,对客户端透明
  • 代理关系在编译期确定,结构清晰,易于理解
  • 增加了中间层,可能会导致性能轻微下降
  • 代理类和目标类需要实现相同接口,如果接口变动,代理类也需要同步修改
  • 一个代理类只能代理一个目标类,如果系统中有大量需要代理的类,会产生大量代理类,增加系统复杂度
动态代理
  • 能够对多个原始实现类进行统一的功能扩展,减少重复代码
  • 代理关系在运行时动态生成,更加灵活
  • 无需为每个目标类单独编写代理类,降低了系统的耦合度
  • 特别适合AOP(面向切面编程)等需要统一横切关注点的场景
  • 增加了中间层,同样可能导致性能下降(反射调用有一定开销)
  • 实现原理相对复杂,理解和调试难度较高
  • 只能代理实现了接口的类(基于JDK动态代理)
  • 生成的代理类代码可读性较差

5.2 代理模式与装饰器模式的区别

  • 扩展维度不同:代理模式强调对于访问的控制,在调用实体类的方法之前或者之后,会添加一些操作,是扩展的广度。装饰器模式强调对于方法的加强,扩展的深度。打个比方,张无忌学习乾坤大挪移。装饰器模式,则增加其深度,从第3层境地学习到第4层境地,注重功能的加强。代理模式则是让其在学习乾坤大挪移之前,先学习一下九阳神功,两种武功能够有所联系。从外界看来,都是武功的增强,但是一个是深度,一个是广度。
  • 可见性不同:代理模式中,在客户看来,无法感知到原始对象,只能接触代理对象。装饰器模式中,客户看来,是能够感知到原始对象的。
http://www.cnnetsun.cn/news/4083102.html

相关文章:

  • 第 4 篇:「当数据库坏了」— 优雅降级与七层防守
  • 《C++深度解构02》类和对象(上)——定义规范、内存对齐与 this 指针全解
  • AI智能体开发指南:企业如何构建智能体系统
  • nxdumptool 终极指南:3 步完成 Switch 卡带与数字游戏的完整备份
  • protal:基于比对的超快速宏基因组物种与菌株分辨分析谱工具
  • League Akari 完整上手指南:本地化英雄联盟辅助工具从安装到精通
  • 装完DistroAV却找不到NDI Source?NDI Runtime缺失的4类场景与彻底排查方案
  • SumatraPDF 颜色反转:5 个让你少走弯路的实用技巧
  • DDrawCompat 使用避坑指南:老游戏打不开,先别急着卸载
  • 三步搞定 DDrawCompat:让 Windows 11 重新跑起二十年前老游戏的完整指南
  • 从 GPT-5.5 迁到 Luna,我的 Agent 工作流哪里断了
  • Git Worktree 实战指南:多分支并行开发与高效工作流设计
  • 2026黄冈危房鉴定检测怎么选?老旧房危房鉴定靠谱机构 TOP 结构安全检测+ 报告可查 电话汇总
  • 突破LLM上下文限制:构建智能体原生记忆系统的分层架构与工程实践
  • arm 解决git 下载代码
  • TNGA架构与双叉臂悬架:深度解析C-HR高速行驶品质的机械奥秘
  • 魔兽争霸3卡顿变形加载失败?这套插件一次治好老游戏的现代病
  • VBA Workbook对象操作全解析:从创建、保存到关闭的自动化实践
  • AI Agent Skill开发实战:从概念到实现,打造智能体核心能力
  • 【重庆邮电大学、重庆蚂蚁消费金融有限公司主办 | 重庆举办】第一届粒球计算国际会议(ICGBC 2026)
  • Linux写论文最头疼的文献管理,被这个WPS-Zotero插件3步搞定了
  • 从单体应用到插件化架构:可组合运行时如何重塑软件开发
  • 从谍照解读新车:广汽传祺GS8 390T动力升级与市场策略分析
  • Godot游戏开发:模块化设计与信号通信实战指南
  • 在Windows Server 2012关闭Internet Explorer增强的安全配置
  • 技术资源分发与社群运营的工程化实践:从加群到自动化体系
  • ORB-SLAM3 optimizer.initializeOptimization(0);
  • 结构型模式-代理模式
  • 魔兽争霸3终极优化指南:三步免费解锁宽屏、高帧率与地图限制
  • 多智能体协同架构在自适应网络安全故障排查中的设计与实践