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层境地注重功能的加强。代理模式则是让其在学习乾坤大挪移之前先学习一下九阳神功两种武功能够有所联系。从外界看来都是武功的增强但是一个是深度一个是广度。可见性不同代理模式中在客户看来无法感知到原始对象只能接触代理对象。装饰器模式中客户看来是能够感知到原始对象的。