模式动机
在某些情况下,一个客户不想或者不能直接引用一个对象,此时可以通过一个称之为“代理”的第三者来实现间接的引用。代理对象可以在客户端和目标对象之间起到中介的作用,并且可以通过代理对象去掉客户端不能看到的内容和服务或者添加客户需要的额外服务。
通过引入一个新的对象(如小图片和远程代理对象)来实现对真实对象的操作或者将新的对象作为真实对象的一个替身,这种实现机制即为代理模式,通过引入代理对象来间接访问一个对象,这就是代理模式的模式动机。
模式定义
代理模式(Proxy Pattern):给某一个对象提供一个代理,并由代理对象控制对原对象的引用,代理模式的英文叫做 Proxy 或 Surrogate ,它是一种对象结构型模式。
模式结构
代理模式包含三种角色
- 抽象主题(Subject):抽象主题是一个接口,该接口是对象和它的代理所共用的接口,即是 RealSubject 角色和 Proxy 角色实例所实现的接口。
- 实际主题(RealSubject):实际主题是实现抽象主题接口的类。实际主题的实例是代理角色(Proxy)实例所要代理的对象。
- 代理(Proxy):代理是实现抽象主题接口的类(代理和实际主题实现了相同的接口)。代理含有主题接口声明的变量,该变量用来存放 RealSubject 角色的实例引用,这样一来,代理的实例就可以控制对它所包含的 RealSubject 角色的实例访问,即可以控制对它所代理对象的访问。
代理模式特点:它与所代理的对象实现了相同的接口,也就是说代理和它所代理的对象向用户公开了相同的方法,当用户请求代理调用这些方法时,代理可能要验证某些信息或检查它所代理的对象是否可用,当代理确认它所代理的对象能调用相同的方法时,就把实际的方法调用委派给它所代理的对象,即让所代理的对象调用相同的方法。
代理模式示意结构图比较简单,一般可以简化为如下图所示,但是在现实中要复杂很多。
为什么要用代理模式
- 中介隔离作用:在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口。
- 开闭原则:代理类除了是客户类和委托类的中介之外,我们还可以通过给代理类增加额外的功能来扩展委托类的功能,这样做我们只需要修改代理类而不需要再修改委托类,符合设计模式的开闭原则。代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后对返回结果的处理等。代理类本身并不真正实现服务,而是通过调用委托类的相关方法,来提供特定的服务。真正的业务功能还是由委托类来实现,但是可以在业务功能执行的前后加入一些公共的服务。例如我们想加入验证传入的参数是否符合特定要求的功能性代码,我们就可以在使用的代理类里添加,而没有必要修改已经封装好的委托类。
代理模式实例与解析
实例一:数据库操作代理
我们需要创建一个 UserManager 接口和实现 UserManager 接口的实体类。StaticProxy 是一个代理类,代理目标对象 UserManagerImpl 类的操作数据库的方法。Client 或调用者通过使用 StaticProxy 来获取目标对象 UserManagerImpl 实例,并可以根据需要在调用方法时进行前置处理与后置处理,结构图如下所示。
创建一个接口。
/**
* 接口方法
* @author 14517
*
*/
public interface UserManager {
public void addUser(String username,String password);
public void deleteUser(int id);
public void modifyUser(int id,String username,String password);
public String findUserById(int id);
}
创建实现接口的实体类。
/**
* 实现 UserManager 接口
* @author 14517
*
*/
public class UserManagerImpl implements UserManager {
@Override
public void addUser(String username, String password) {
System.out.println("---UserManagerImpl addUser()---");
}
@Override
public void deleteUser(int id) {
System.out.println("---UserManagerImpl deleteUser()---");
}
@Override
public void modifyUser(int id, String username, String password) {
System.out.println("---UserManagerImpl modifyUser()---");
}
@Override
public String findUserById(int id) {
System.out.println("---UserManagerImpl findUserById()---");
return null;
}
}
创建 UserManager 代理类,使用代理类来获取 UserManager 类的实例,然后调用目标对象中的方法进行业务逻辑处理。
/**
* 静态代理模式 (1.实现接口 2.调用接口) 需针对每一个接口编写代理类,工作量巨大
* 在不修改原代码的前提下,使功能发生改变
* 在方法执行之前或执行之后做处理
* @author 14517
*
*/
public class StaticProxy implements UserManager {
private UserManager userManager;
public StaticProxy(UserManager userManager) {
this.userManager=userManager;
}
@Override
public void addUser(String username, String password) {
checkBefore();
userManager.addUser(username, password);
checkAfter();
}
@Override
public void deleteUser(int id) {
checkBefore();
userManager.deleteUser(id);
checkAfter();
}
@Override
public void modifyUser(int id, String username, String password) {
checkBefore();
userManager.modifyUser(id, username, password);
checkAfter();
}
@Override
public String findUserById(int id) {
checkBefore();
userManager.findUserById(id);
checkAfter();
return null;
}
/**
* 方法执行之前调用
*/
public void checkBefore() {
System.out.println("Before---");
}
/**
* 方法执行之后调用
*/
public void checkAfter() {
System.out.println("After---");
}
}
测试代理模式。
/**
* 静态代理测试
* @author 14517
*
*/
public class StaticProxyTest {
public static void main(String[] args) {
UserManager um=new StaticProxy(new UserManagerImpl());
um.addUser("StaticProxy", " ");
um.deleteUser(1);
um.modifyUser(1, "StaticProxy", " ");
um.findUserById(1);
}
}
执行程序,输出结果:
实例二:论坛权限控制代理
在一个论坛中已经注册用户和游客的权限不同,已注册的用户拥有发帖、修改自己的注册信息、修改自己的帖子等功能;而游客只能看到别人发的帖子,没有其他权限。使用代理模式来设计该权限管理模块。
在本实例中,我们使用代理模式中的保护代理,该代理用于控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限。
实例三:数学运算代理
模拟应用远程代理来访问另一个应用程序域中的对象,如果在远程实现了加减乘除等运算,在本地需要调用,那么可以考虑在本地设置一个代理。
代理模式优缺点
优点
1. 可以做到在不修改目标对象的功能前提下,对目标功能扩展。
2. 代理模式能够协调调用者和被调用者,在一定程度上降低了系统的耦合度。
3. 远程代理使得客户端可以访问在远程机器上的对象,远程机器可能具有更好的计算性能与处理速度,可以快速响应并处理客户端请求。
4. 虚拟代理通过使用一个小对象来代表一个大对象,可以减少系统资源的消耗,对系统进行优化并提高运行速度。
5. 保护代理可以控制对真实对象的使用权限。
缺点
1. 静态代理由于需要和目标对象实现相同的接口,当代理对象变多的时候,代理类就会跟着增加,而且一旦更改了接口,那么目标对象和代理对象都要同时做出调整,不方便管理。
2. 由于客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。
3. 实现代理模式需要额外的工作,有些代理模式的实现非常复杂。
模式适用环境
根据代理模式的使用目的,常见的代理模式有以下几种类型:
1. 远程(Remote)代理:为一个位于不同的地址空间的对象提供一个本地的代理对象,这个不同的地址空间可以是在同一台主机中,也可是在另一台主机中。
2. 虚拟(Virtual)代理:如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。
3. Copy-on-Write 代理:它是虚拟代理的一种,把复制(克隆)操作延迟到只有在客户端真正需要时才执行。一般来说,对象的深克隆是一个开销较大的操作,Copy-on-Write 代理可以让这个操作延迟,只有对象被用到的时候才被克隆。
4. 保护(Protect or Access)代理:控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限。
5. 缓冲(Cache)代理:为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。
6. 防火墙(Firewall)代理:保护目标不让恶意用户接近。
7. 同步化(Synchronization)代理:使几个用户能够同时使用一个对象而没有冲突。
8. 智能引用(Smart Reference)代理:当一个对象被引用时,提供一些额外的操作,如将此对象被调用的次数纪录下来等。
模式应用
1. Java RMI (Remote Method Invocation),远程方法调用。
2. EJB、Web Service 等分布式技术都是代理模式的应用。在 EJB 中使用了 RMI 机制,远程服务器中的企业级 Bean 在本地有一个桩代理,客户端通过桩来调用远程对象中定义的方法,而无需直接与远程对象交互。在 EJB 的使用中需要提供一个公共的接口,客户端针对该接口进行编程,无需知道桩以及远程 EJB 的实现细节。
3. Spring 框架中的 AOP 技术也是代理模式的应用,在 Spring AOP 中应用了动态代理(Dynamic Proxy)技术。详情请参考https://blog.csdn.net/weixin_39453325/article/details/84309742这篇博客
模式扩展
几种常见的代理模式:
1. 图片代理:一个很常见的代理模式的应用实例就是对大图浏览的控制。用户通过浏览器访问网页时先不加载真实的大图,而是通过代理对象的方法来进行处理,在代理对象的方法中,先使用一个线程向客户端浏览器加载一个小图片,然后在后台使用另一个线程来调用大图片的加载方法将大图片加载到客户端。当需要浏览大图片时,再将大图片在新网页中显示。如果用户在浏览大图时加载工作还没有完成,可以再启动一个线程来显示相应的提示信息。通过代理技术结合多线程编程将真实图片的加载放到后台来操作,不影响前台图片的浏览。
2. 远程代理:远程代理可以将网络的细节隐藏起来,使得客户端不必考虑网络的存在,客户完全可以认为被代理的远程业务对象是局域网的而不是远程的,而远程代理对象承担了大部分的网络通信工作。
3. 虚拟代理:当一个对象的加载十分耗费资源的时候,虚拟代理的优势就非常明显地体现出来了。虚拟代理模式是一种内存节省技术,那些占用大量内存或处理复杂的对象将推迟到使用它的时候才创建。在应用程序启动的时候,可以用代理对象代替真实对象初始化,节省了内存的占用,并大大加速了系统的启动时间。
4. 动态代理:详情请参考https://blog.csdn.net/weixin_39453325/article/details/84309742这篇博客