代理模式:一种结构性设计模式的全面解析
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/864fdf0d13ff41ec8f19b73703552764.png)
一、代理模式简介
1.1 概念
代理模式(Proxy Pattern
)属于结构型设计模式,其核心思想是为其他对象提供一种代理以控制对这个对象的访问。代理对象在客户端和目标对象之间起到中介的作用,并可以在不改变客户端代码的情况下增强或控制对象的访问。
1.2 模式类型
结构性
1.3 代理模式的类别
代理模式主要分为三种类型:
- 静态代理 :由程序员创建或第三方工具生成代理类的源代码,再进行编译。 其代理类和委托类在编译期间就确定下来。
- 动态代理 : 代理类在程序运行时通过反射等机制动态生成,无需程序员显式地创建代理类。
JDK
动态代理CGLib
动态代理
- 虚拟代理 : 当有一个需要创建开销较大的对象时,通过虚拟代理来存放实例化需要较长时间的真实对象。
1.4 优点
- 控制访问:代理模式可以控制对对象的访问,可以在访问对象之前或之后执行一些额外的操作。例如:日志记录、功能增强、权限验证、性能监控等。
- 保护对象:代理模式可以对真实对象进行保护,只允许特定的客户端访问真实对象,从而提供了一定的安全性。
- 延迟加载:代理模式可以延迟加载真实对象,当需要真实对象时才进行创建和初始化,从而提高了系统的性能和资源利用率。
- 简化客户端:代理模式隐藏了真实对象的复杂性,客户端可以通过代理对象来完成操作,无需直接与真实对象交互。
1.5 缺点
- 增加复杂性:因为引入了代理对象,使得对对象的管理和维护更加复杂。
- 性能损耗:由于代理模式在访问对象时引入了额外的间接层,可能会导致性能上的损耗。每次通过代理访问对象都需要经过额外的处理步骤,可能会对系统的响应时间产生影响。
二、代理模式的模式动机
代理模式的模式动机是为了提供一个中间层(代理对象)来控制对另一个对象的访问,以满足一些特定的需求。
三、模式结构
代理模式主要包括以下角色:
- 抽象主题角色(
Subject
):通过接口或抽象类声明真实主题和代理的公共方法。 - 真实主题角色(
Proxy
):实现抽象主题角色,定义代理所代表的真实对象。 - 代理主题角色(
RealSubject
):包含对真实主题的引用,从而可以操作真实主题对象;同时可以提供额外的功能来扩展真实主题的行为。
四、代理模式的实现
4.1 静态代理
- 定义抽象接口:视频播放器接口
Player
public interface Player { void loadVideo(String filename); void playVideo(String filename); }
- 定义真实对象:
RealPlayer
public class RealPlayer implements Player { @Override public void loadVideo(String filename) { System.out.println("加载MP4视频文件: " + filename); } @Override public void playVideo(String filename) { System.out.println("播放MP4视频文件: " + filename); } }
- 定义代理对象:
PlayerProxy
public class PlayerProxy implements Player{ private RealPlayer realPlayer; public PlayerProxy(RealPlayer realPlayer){ this.realPlayer = realPlayer; } @Override public void loadVideo(String filename) { realPlayer.loadVideo(filename); } @Override public void playVideo(String filename) { realPlayer.playVideo(filename); } }
- 客户端
public class Client { public static void main(String[] args) { // 直接调用 RealPlayer realPlayer = new RealPlayer(); realPlayer.playVideo("《西游记》.mp4"); System.out.println("==========================="); // 代理方式调用,代理真实对象 PlayerProxy playerProxy = new PlayerProxy(realPlayer); playerProxy.loadVideo("《三国演义》.mp4"); playerProxy.playVideo("《三国演义》.mp4"); } }
- 结果
4.2 JDK 动态代理🚀🚀🚀
JDK
动态代理可以在运行时动态生成一个代理对象,是 Java
标准库中提供的一种代理方法,代理对象实现和原始类一样的接口,并将方法调用转发给被代理对象,同时还可以在方法调用前后执行额外的增强处理。JDK
动态代理通过反射机制实现代理功能。
4.2.1 原理:
- 创建实现
InvocationHandler
接口的代理类工厂:在调用Proxy
类的静态方法newProxyInstance
时,会动态生成一个代理类。该代理类实现了目标接口,并持有一个InvocationHandler
类型的引用。 InvocationHandler
接口:InvocationHandler
是一个接口,并且只有一个方法invoke
。在代理对象的方法被调用时,JVM
会自动调用代理类的invoke
方法,并将调用的方法名、参数等信息传递给该方法。- 调用代理对象的方法:当代理对象的方法被调用时,JVM 会自动调用代理类的
invoke
方法。可以根据需要执行各种逻辑,比如添加日志、性能统计、事物管理等; invoke
方法调用: 在invoke
方法中,通过反射机制调用目标对象的方法,并返回方法的返回值。在调用目标对象的方法前后,可以执行额外的逻辑。
4.2.2 实现:
-
创建代理工厂类
public class JDKProxyFactory implements InvocationHandler { // 需要被代理的对象 private Object object; public JDKProxyFactory(Object object) { this.object = object; } @SuppressWarnings("unchecked") public <T> T getProxy() { return (T) Proxy.newProxyInstance( // 当前线程的上下文类加载器 Thread.currentThread().getContextClassLoader(), // 被代理对象的接口 object.getClass().getInterfaces(), // 处理器自身 this ); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result = null; // 进行方法匹配,调用对应方法名的方法 if ("loadVideo".equals(method.getName())) { result = method.invoke(object, args); } if ("playVideo".equals(method.getName())) { System.out.println("前置增强"); result = method.invoke(object, args); System.out.println("后置增强"); } return result; } }
-
客户端
public class Client { public static void main(String[] args) { RealPlayer realPlayer = new RealPlayer(); // 通过代理工厂类获取代理对象,代理 realPlayer Player proxy = new JDKProxyFactory(realPlayer).getProxy(); proxy.loadVideo("《红楼梦》.mp4"); proxy.playVideo("《红楼梦》.mp4"); } }
-
结果
4.3 CGLib 动态代理
CGLib
(Code Generation Library)是一个基于 ASM
(Java 字节码操作框架)实现的代理生成库,它可以在运行时动态生成目标类的子类作为代理类,并覆盖其中的方法来实现代理功能。与 Java 自带的JDK 动态代理不同,CGlib
动态代理可以代理没有实现接口的类。
4.3.1 原理:
- 创建
Enhancer
对象:Enhancer 是 CGLib 库中用于动态生成子类的主要类。通过创建 Enhancer 对象并设置需要代理的目标类、拦截器等参数,可以生成一个代理类。 - 设置回调拦截器:在生成代理类时,需要指定拦截器。拦截器是实现代理逻辑的关键,它会在代理类的方法被调用时拦截调用,并执行相应的逻辑。在 CGLib 中,拦截器需要实现
MethodInterceptor
接口。 - 创建代理对象:通过调用
Enhancer
对象的create
方法,可以生成一个代理对象。代理对象会继承目标类的方法,并且在调用地阿里对象的方法时会先调用拦截器的interceptor
方法,再执行目标方法。 - 调用代理对象:通过调用代理对象的方法,会触发拦截器的
interceptor
方法。在interceptor
方法中,可以根据需要执行各种逻辑,比如添加日志、性能统计、事物管理等。
4.3.2 步骤
-
定义一个真实对象 RealPlayer,注意这块的 RealPlayer 没有实现接口
public class RealPlayer { public void loadVideo(String filename) { System.out.println("加载MP4视频文件: " + filename); } public void playVideo(String filename) { System.out.println("播放MP4视频文件: " + filename); } }
-
创建CGLib代理工厂
public class CglibProxyFactory implements MethodInterceptor { public <T> T getProxy(Class<T> clazz) { Enhancer en = new Enhancer(); // 设置代理的父类 en.setSuperclass(clazz); // 设置方法回调 en.setCallback(this); // 创建代理实例 return (T) en.create(); } /** * @param obj 目标对象 * @param method 目标对象的方法 * @param args 目标对象的参数 * @param proxy 方法代理 * @return 结果 * @throws Throwable */ @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { Object result = null; if ("loadVideo".equals(method.getName())) { // 通过继承的方法实现代理,因此这里调用 invokeSuper result = proxy.invokeSuper(obj, args); } if ("playVideo".equals(method.getName())) { result = proxy.invokeSuper(obj, args); } return result; } }
-
客户端
public class Client { public static void main(String[] args) { RealPlayer realPlayer = new RealPlayer(); RealPlayer proxy = new CglibProxyFactory().getProxy(realPlayer.getClass()); // 验证代理类的父类 System.out.println("代理类的父类 : " + proxy.getClass().getSuperclass().getSimpleName()); System.out.println(); proxy.loadVideo("自由飞翔.mp4"); proxy.playVideo("自由飞翔.mp4"); } }
-
结果
五、代理模式的应用场景
5.1 适用场景
- 远程代理(
Remote Proxy
):为位于不同地址空间的对象提供本地代表,使得客户端可以透明地调用远程对象的方法,就像调用本地对象一样。这常用于分布式系统中,减少网络通信的复杂度。 - 虚拟代理(
Virtual Proxy
):当创建一个对象的开销很大或者需要很长时间时,可以使用虚拟代理来延迟对象的创建,直到真正需要它的时候。例如,上面的代码片段中,如果RealPlayer
加载视频非常耗时,可以使用代理在真正播放前才加载视频。 - 保护代理(
Protective Proxy
):控制对敏感对象的访问,例如,基于用户权限限制对某些资源的访问。代理可以添加额外的安全检查逻辑。 - 缓存代理(
Caching Proxy
):为结果被频繁请求的对象提供缓存,以减少重复的计算或访问开销。例如,如果视频播放器经常重复播放同一视频,代理可以缓存视频数据,避免重复加载。 - 日志代理(
Logging Proxy
):在方法调用前后自动记录日志,方便调试和监控。例如,在调用loadVideo和playVideo方法前后打印日志信息。 - 懒加载代理(
Lazy Loading Proxy
):推迟对象的初始化直到真正需要使用它的时候,这可以提升程序启动速度和节省资源。 - 增强功能代理(
Enhancement Proxy
):在不修改原有对象的基础上,为对象添加额外的功能,比如事务管理、异常处理等,如代码中的CglibProxyFactory
可能为RealPlayer
添加了额外的逻辑。
5.2 应用实例🚀🚀🚀
SpringAOP
- RPC:
RPC
(远程代理)框架也可以看作一种代理模式,通过远程代理,将网络通信、数据编码解码等细节隐藏起来。客户端在使用 RPC 服务的时候,就像使用本地函数一样,无需了解根服务器交互的细节。除此之外,RPC服务的开发者也只需要开发业务逻辑,就像开发本地使用的函数一样,不需要关注跟客户端的交互细节。