1. 概述
代理模式是一种结构型设计模式, 让你能够提供对象的替代品或其占位符。 代理控制着对于原对象的访问, 并允许在将请求提交给对象前后进行一些处理。
代理模式,相信大家都不陌生啦,反射和spring里面就是用到了代理模式,不管你学没学过,大家再来深入了解一下代理模式。
老惯例从问题引入。先来看下代理模式的概念:为某些对象提供一种代理以控制对这个对象的访问。为什么要控制某些的对象的访问呢?举个例子: 有这样一个消耗大量系统资源的巨型对象, 你只是偶尔需要使用它, 并非总是需要。该怎么解决呢?
很简单,你可能想到了懒加载(延迟加载),在实际有需要时再创建该对象。 但是这样存在一个问题:这很可能会带来很多重复代码,因为客户端所有使用到这个对象的地方都要实现延迟加载。
那你可能会说,那我把延迟加载不放在客户端,反正巨型对象的代码里面,那我客户端就不必写重复代码了。但问题又来了,这样确实可以解决重复代码的问题,但比如类可能是第三方封闭库的一部分,我们就不能把延迟加载弄到里面去了。(当然,你直接改源码的话当我没说)
代理模式建议新建一个与原服务对象接口相同的代理类, 然后更新应用以将代理对象传递给所有原始对象客户端。 代理类接收到客户端请求后会创建实际的服务对象, 并将所有工作委派给它。
简单说,就是我们既然不能直接修改巨型对象,那我们就新建一个东西,它具有我们想要增强的功能(上面的例子中指延迟加载功能),同时它也能调用巨型对象进行工作,就是在原来的基础上加上了一层。
比如下图,我们把数据库看成一个巨型对象,客户端想要实现延迟加载,但是又不能直接改数据库的代码。所以我们新建了一个代理对象,它能调用数据库的功能,同时我们也把延迟加载的代码放到代理对象里面去,以后客户端与代理对象交互就可,这样就同时实现了数据库的功能和延迟加载功能。
如果上面延迟加载的例子还不足以让你理解,再举一个例子,假如现在有某个需求需要强化数据库的某一个功能,OK那就又回到上面的情况,要么写在客户端造成重复代码,要么改数据库的代码,这次你很强硬哈,你就要改源码,okk那可以。
这时又来了一个新需求,但是你发现它做不出来,因为你改了源码,有一些东西被你改掉了,所以你此时你该怎么办?代理模式来帮你,新建一个代理对象,把要增强的功能写在代理对象里,这样就解决了。这个就是代理模式,应该可以理解了吧,还算是蛮简单的。
- 总结
- 代理模式新增了一个代理对象,用于控制被代理对象的访问,同时也能增强被代理的对象的某些功能
2. 特点
- 优点
- 你可以在客户端毫无察觉的情况下控制服务对象。
- 如果客户端对服务对象的生命周期没有特殊要求, 你可以对生命周期进行管理。
- 即使服务对象还未准备好或不存在, 代理也可以正常工作。
- 开闭原则。 你可以在不对服务或客户端做出修改的情况下创建新代理。
- 缺点
- 代码可能会变得复杂, 因为需要新建许多类。
- 服务响应可能会延迟。
- 使用场景
-
延迟初始化 (虚拟代理)。 如果你有一个偶尔使用的重量级服务对象, 一直保持该对象运行会消耗系统资源时, 可使用代理模式。你无需在程序启动时就创建该对象, 可将对象的初始化延迟到真正有需要的时候。
-
访问控制 (保护代理)。 如果你只希望特定客户端使用服务对象, 这里的对象可以是操作系统中非常重要的部分, 而客户端则是各种已启动的程序 (包括恶意程序), 此时可使用代理模式。代理可仅在客户端凭据满足要求时将请求传递给服务对象。
-
本地执行远程服务 (远程代理)。 适用于服务对象位于远程服务器上的情形。在这种情形中, 代理通过网络传递客户端请求, 负责处理所有与网络相关的复杂细节。
-
记录日志请求 (日志记录代理)。 适用于当你需要保存对于服务对象的请求历史记录时。 代理可以在向服务传递请求前进行记录。
-
缓存请求结果 (缓存代理)。 适用于需要缓存客户请求结果并对缓存生命周期进行管理时, 特别是当返回结果的体积非常大时。代理可对重复请求所需的相同结果进行缓存, 还可使用请求参数作为索引缓存的键值。
-
智能引用。 可在没有客户端使用某个重量级对象时立即销毁该对象。
代理会将所有获取了指向服务对象或其结果的客户端记录在案。 代理会时不时地遍历各个客户端, 检查它们是否仍在运行。 如果相应的客户端列表为空, 代理就会销毁该服务对象, 释放底层系统资源。代理还可以记录客户端是否修改了服务对象。 其他客户端还可以复用未修改的对象。
-
3. 实现
3.1 静态代理
-
UML类图
-
角色说明
- 被代理接口:被代理的接口,定义了要增强的功能,当然这里也可以是抽象类,那代理对象就要继承这个类
- 被代理实现类:被代理的具体对象,实现了具体逻辑
- 代理对象:实现了被代理接口或者继承被代理抽象类,需要内聚一个被代理接口或者被代理抽象类,重写方法,对它们进行增强
- 客户端:调用代理模式的角色
-
Java代码
- 被代理接口
/** * @Author: chy * @Description: 被代理接口,数据库接口 * @Date: Create in 13:59 2021/3/17 */ public interface IDatabase { void use(); }
- 被代理实现类
/** * @Author: chy * @Description: 被代理对象,数据库接口实现类 * @Date: Create in 14:00 2021/3/17 */ public class Database implements IDatabase{ @Override public void use() { System.out.println("数据库正在使用。。。。。"); } }
- 代理对象
/** * @Author: chy * @Description: 代理类 * @Date: Create in 14:01 2021/3/17 */ public class Proxy implements IDatabase{ private IDatabase database; public Proxy(IDatabase database) { this.database = database; } @Override public void use() { System.out.println("延迟加载。。。。"); database.use(); } }
- 客户端
/** * @Author: chy * @Description: 客户端 * @Date: Create in 13:59 2021/3/17 */ public class Client { public static void main(String[] args) { Proxy proxy = new Proxy(new Database()); proxy.use(); } }
- 结果
- 被代理接口
3.2 动态代理
动态代理和静态代理其实差不多,原理都是一样的,不一样的点就是静态代理是需要自己新建一个代理对象的,如上面例子里的Proxy
类,动态代理不用自己新建代理对象,而是交给反射机制来帮我们新建。
有两种:JDK代理和Cglib代理,jdk就是Java提供给我们的,cglib代理则需要我们自己导入jar包,jdk代理是基于接口的,cglib代理是基于类的,它们的角色和别的操作都和静态代理一样,就代理对象使用方法不同。具体这里就不贴出代码了,可以去查查它们的具体用法。