代理模式(Proxy Pattern)
一、模式动机与定义
- 代理模式的作用是:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,
一个客户不想或者不能直接引用一个对象,此时可以通过一个称之为“代理”的第三者来实现间接引用
。(在程序中,对象A和对象B无法直接交互时。)代理对象可以在客户端和目标对象之间起到中介的作用,并且可以通过代理对象去掉客户不应该看到的内容和服务或者添加客户需要的额外服务
(在程序中,目标需要被保护时或者功能增强)。 通过引入一个新的对象来实现对真实对象的操作或者将新的对象作为真实对象的一个替身,这种实现机制即为代理模式,通过引入代理对象来间接访问一个对象,这就是代理模式的模式动机。 代理模式(Proxy Pattern)
的定义:给某个对象提供一个代理,并由代理对象控制对原对象的引用。代理模式的英文叫做Proxy
或Surrogate
,它是一种对象结构型模式。- 英文定义:“Provide a surrogate or placeholder for another object to control access to it.”
二、模式结构与分析
- 代理模式中的角色:
- 代理类(代理主题)
- 目标类(真实主题)
- 代理类和目标类的公共接口(抽象主题):抽象主题角色声明了真实主题和代理主题的公共接口,这样一来在任何使用真实主题的地方都可以使用代理主题。客户端需要针对抽象主题角色进行编程。
三、静态代理
- 现在有这样一个接口和实现类:
/**
* 订单接口
*/
public interface OrderService {
/**
* 生成订单
*/
void generate();
/**
* 查看订单
*/
String detail();
/**
*删除订单
*/
void delete();
}
/**
* OrderService 的实现类
*/
public class OrderServiceImpl implements OrderService {
@Override
public void generate() {
//模拟生成订单的耗时
try {
Thread.sleep(1345);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("订单已生成....");
}
@Override
public String detail() {
//模拟查询订单的耗时
try {
Thread.sleep(453);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("正在查询订单信息....");
return "订单详情";
}
@Override
public void delete() {
//模拟删除订单的耗时
try {
Thread.sleep(1234);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("正在删除订单....");
}
}
- 需求:项目已上线,并且运行正常,只是客户反馈系统有一些地方运行较慢,要求项目组对系统进行优化。于是项目负责人就下达了这个需求。首先需要搞清楚是哪些业务方法耗时较长,于是让我们统计每个业务方法所耗费的时长。
- 第一种方案:直接修改Java源代码,在每个业务方法中添加统计逻辑,如下:
public class OrderServiceImpl implements OrderService {
@Override
public void generate() {
long begin = System.currentTimeMillis();
//模拟生成订单的耗时
try {
Thread.sleep(1345);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("订单已生成....");
long end = System.currentTimeMillis();
System.out.println("耗费时常" + (end - begin) + "毫秒");
}
@Override
public String detail() {
long begin = System.currentTimeMillis();
//模拟查询订单的耗时
try {
Thread.sleep(453);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("正在查询订单信息....");
long end = System.currentTimeMillis();
System.out.println("耗费时常" + (end - begin) + "毫秒");
return "订单详情";
}
@Override
public void delete() {
long begin = System.currentTimeMillis();
//模拟删除订单的耗时
try {
Thread.sleep(1234);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("正在删除订单....");
long end = System.currentTimeMillis();
System.out.println("耗费时常" + (end - begin) + "毫秒");
}
}
缺点一:违背了 OCP 开闭原则。
缺点二:代码没有得到复用。(相同的代码写了很多遍)
缺点三:如果有很多这样的业务方法的话,需要改写很多的类。
- 第二种方案:编写一个子类继承OrderServiceImpl,在子类中重写每个方法,代码如下:
/**
* OrderServiceImpl 类的子类
*/
public class OrderServiceImplSub extends OrderServiceImpl{
@Override
public void generate() {
long begin = System.currentTimeMillis();
super.generate();
long end = System.currentTimeMillis();
System.out.println("耗费时常" + (end - begin) + "毫秒");
}
@Override
public String detail() {
long begin = System.currentTimeMillis();
String detail = super.detail();
long end = System.currentTimeMillis();
System.out.println("耗费时常" + (end - begin) + "毫秒");
return detail;
}
@Override
public void delete() {
long begin = System.currentTimeMillis();
super.delete();
long end = System.currentTimeMillis();
System.out.println("耗费时常" + (end - begin) + "毫秒");
}
}
缺点一:虽然解决 OCP 开闭原则。但是这种方式会导致耦合度很高,因为采用了继承关系。继承关系是一种耦合度非常高的关系,不建议使用。
缺点二:代码没有得到复用。(相同的代码写了很多遍)
缺点三:如果有很多这样的业务方法的话,需要编写很多的继承类,会类爆炸。
- 第三种方案:使用代理模式(这里采用静态代理)
- 可以为OrderService接口提供一个代理类。
/**
* OrderServiceImpl的代理对象
*/
public class OrderServiceProxy implements OrderService{
// 目标对象
private OrderService orderService;
// 通过构造方法将目标对象传递给代理对象
public OrderServiceProxy(OrderService orderService) {
this.orderService = orderService;
}
@Override
public void generate() {
long begin = System.currentTimeMillis();
orderService.generate();
long end = System.currentTimeMillis();
System.out.println("耗费时常" + (end - begin) + "毫秒");
}
@Override
public String detail() {
long begin = System.currentTimeMillis();
String detail = orderService.detail();
long end = System.currentTimeMillis();
System.out.println("耗费时常" + (end - begin) + "毫秒");
return detail;
}
@Override
public void delete() {
long begin = System.currentTimeMillis();
orderService.delete();
long end = System.currentTimeMillis();
System.out.println("耗费时常" + (end - begin) + "毫秒");
}
}
- 编写客户端程序:
public class Client {
public static void main(String[] args) {
OrderService orderService = new OrderServiceImpl();
OrderService orderServiceProxy = new OrderServiceProxy(orderService);
orderServiceProxy.generate();
orderServiceProxy.detail();
orderServiceProxy.delete();
}
}
优点一:解决了 OCP。
优点二:采用代理模式的 has a(关联关系),可以降低耦合度。
缺点一:如果有很多这样的业务方法的话,需要编写很多这样的代理类,会类爆炸。
- 以上就是代理模式中的静态代理,其中OrderService接口是代理类和目标类的共同接口。OrderServiceImpl是目标类。OrderServiceProxy是代理类。
- 动态代理模式,还是代理模式,只不过添加了字节码生成技术,可以在内容中为我们动态的生成一个 Class 字节码,这个字节码就是代理类。
- 在内存中动态的生成字节码代理类的技术,叫做:动态代理。
四、动态代理
- 详情见我的博客 ===> 动态代理详解
五、代理模式效果与应用
1.模式的优缺点
(1)代理模式的优点
- ① 代理模式能够协调调用者和被测用者,在一定程度上降低了系统的耦合度
- ② 远程代理使得客户端可以访问在远程机器上的对象,远程机器可能具有更好的计算性能与处理速度,可以快速响应并处理客户端请求。
- ③ 虚拟代理通过使用一个小对象来代表一个大对象,可以减少系统资源的消耗,对系统进行优化并提高运行速度。
- ④ 保护代理可以控制对真实对象的使用权限。
(2)代理模式的缺点
- ① 由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。
- ② 实现代理模式需要额外的工作,有些代理模式的实现非常复杂。
2.模式适用环境
- ①
远程(Remote)代理
,为一个位于不同的地址空间的对象提供一个本地的代理对象,这个不同的地址空间可以是在同一台主机中,也可是在另一台主机中,远程代理又叫做大使(Ambassador)。 - ②
虚拟(Virtual)代理
,如果需要创建一个源消耗较大的对象,先建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。 - ③
Copy-on-Write 代理
,它是虚拟代理的一种,把复制(克隆)操作延迟到只有在客户真正需要时才执行。一般来说,对象的深克隆是一个开销较大的操作,Copyon-Wtite 代代理可以让这个操作延迟,只有对象被用到的时候才被克降。 - ④
保护(Protect or Access)代理
, 控制对一个对象的访间,可以给不同的用户提供不别的使用权限。 - ⑤
缓冲(Cache)代理
,为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。 - ⑥ 防火墙
(Firewall)代理
,保护目标不让恶意用户接近。 - ⑦
同步化(Synchronization)代理
,使几个用户能够同时使用一个对象而没有冲突。 - ⑧
智能引用(Smart Reference)代理
,当一个对象被引用时,提供一些额外的操作,如将此对象被调用的次数记录下来等。 总结
:在这些种类的代理中,虚拟代理
、远程代理
和保护代理
是最常见的代理模式。不同类型的代理模式有不同的优缺点,它们应用于不同的场合
3.模式应用
(1)Java 的 RMI(Remote Method Invocation)
- 远程调用方法(
Remote Method Incocation
)中,定义了客户对象和远程对象,其中客户对象(Client Object
)在客户端运行,向远程对象发送请求;远程对象(Remote Object
)在服务器端运行,通过客户对象使得远程对象如同本地对象一样被访问。 - 在
Java RM
中客户对象称为Stub
(翻译为存根或桩),桩就是远程对象在客户端的代理,客户进程中的远程对象引用实际上是对本地桩的引用,桩负责调用客户请求发送给远程代理对象。
(2)EJB、Web Service 等分布技术
EJB
、Web Service
等分布技术都是代理模式的应用。在 EJB 中使用了 RMI 机制,远程服务器中的企业级 Bean 在本地有一个桩代理,客户端通过桩调用远程对象中定义的方法,而无须直接与远程对象交互。在 EJB 的使用中需要提供一个公共的接口,客户端针对接口编程,无须知道桩以及远程 EJB 的实现细节。
六、代理模式扩展
1.图片代理
- 一个很常见的代理模式的应用实例就是对大图浏览的控制。如在网页中先提供一个小图片,当用户需要查看大图时再通过点击图片来激活一个链接,在一个新的网页中打开需要浏览的大图片,这对于提高浏览器速度很有好处,可以使用代理模式对这个过程进行进一步优化。用户通过浏览器访问网页时先不加载真实的大图,而是通过代理对象的方法来进行处理,在代理对象的方法中,先使用一个线程向客户端浏览器加载一个小图片,然后在后台使用另一个线程来调用大图片的加载方法将大图片加载到客户端。当需要浏览大图片时,再将大图片在新网页中显示。如果用户在浏览大图时加载工作还没有完成,可以再启动一个线程来显示相应的提示信息。通过代理技术结合多线程编程将真实图片的加载放到后台来操作,不影响前台图片的浏览。
2.远程代理
远程代理可以将网络的细节隐藏起来,使得客户端不必考虑网络的存在
。客户完全可以认为被代理的远程业务对象是局域的而不是远程的,而远程代理对象承担了大部分的网络通信工作。远程代理模式如下图,客户端对象不能直接直接访问远程主机中的业务对象,只能通过本地主机间接访问,远程业务对象在本地主机中有一个远程代理对象,它负责对远程业务对象的访问和网络通信,对于客户端而言是透明的,客户端无需关心实现具体业务的是谁,它只需要按照服务接口所定义的方式直接与本地主机交互即可。
3.虚拟代理
当一个对象的加载十分耗费资源的时候
,虚拟代理
的优势就非常明显地体现出来了。虚拟代理 模式是一种内存节省技术,那些占用大量内存或处理复杂的对象将推迟到使用它的时候才创建。
对象在第一次被引用时被创建并且同一对象可被重用
,它加速了应用程序的启动,但是由于在访问时需要检测所需对象是否已经被创建,因此在访问该对象的任何地方都需要进行存在性检验,将消耗系统时间,这也是用时间换取空间的一种做法。- 在应用虚拟代理模式时,需要设计一个与真实对象具有相同接口的虚拟对象,虚拟对象把真实对象的引用作为它的实例变量进行维护,不同的客户对象可以在创建和使用真实对象地方用相应的虚拟对象来代替。代理对象不需要自动创建真实对象,当客户需要真实对象的服务时,可以调用虚拟对象上的方法来创建,并且在使用过程中还可以检测真实对象是否被创建。
- 如果真实对象已经创建,代理对象就把调用转发给真实对象;如果真实对象没有被创建,代理对象首先创建真实对象,代理对象再把这个对象分配给引用变量,最后代理对象把调用转发给真实对象。在这个过程中,
验证对象存在和转发方法调用这些细节对于客户是不可见的,客户对象就像和真实对象一样与代理对象进行交互
。因此客户可以从检测真实对象是否存在中解脱出来,另外,由于创建代理对象在时间和处理复杂度上要少于创建真实对象,因此,在应用程序启动的时候,可以用代理对象代替真实对象初始化,节省了内存的占用,并大大加速了系统的启动时间
。