代理模式(Proxy Pattern),由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。
主要解决在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。
代理模式的组成如下:
| Subject(抽象主题) | 通过接口或抽象类声明真实主题和代理对象实现的业务方法 |
|---|---|
| Real Subject(真实主题) | 实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象 |
| Proxy(代理类) | 提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能 |
根据代理的创建时期,代理模式分为静态代理和动态代理。
- 静态:由程序员创建代理类或特定工具自动生成源代码再对其编译,在程序运行前代理类的 .class 文件就已经存在了
- 动态:在程序运行时,运用反射机制动态创建而成
代理模式的优点
- 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
- 代理对象可以扩展目标对象的功能;
- 代理模式能将客户端与目标对象分离,在一定程度上降低了系统的耦合度,增加了程序的可扩展性
代理模式的缺点
- 代理模式会造成系统设计中类的数量增加
- 在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢;
- 增加了系统的复杂度;
代理模式使用场景
当无法或不想直接引用某个对象或访问某个对象存在困难时,可以通过代理对象来间接访问。
使用代理模式主要有两个目的:一是保护目标对象,二是增强目标对象
**
代理模式按照职责来划分的话,通常有以下使用场景:
远程代理
远程代理即Remote Proxy,本地的调用者持有的接口实际上是一个代理,这个代理负责把对接口的方法访问转换成远程调用,然后返回结果。
通常是为了隐藏目标对象存在于不同地址空间的事实,方便客户端访问。例如,用户申请某些网盘空间时,会在用户的文件系统中建立一个虚拟的硬盘,用户访问虚拟硬盘时实际访问的是网盘空间。
虚代理
虚代理即Virtual Proxy,它让调用者先持有一个代理对象,但真正的对象尚未创建。如果没有必要,这个真正的对象是不会被创建的,直到客户端需要真的必须调用时,才创建真正的对象。JDBC的连接池返回的JDBC连接(Connection对象)就可以是一个虚代理,即获取连接时根本没有任何实际的数据库连接,直到第一次执行JDBC查询或更新操作时,才真正创建实际的JDBC连接。
这种方式通常用于要创建的目标对象开销很大时。例如,下载一幅很大的图像需要很长时间,因某种计算比较复杂而短时间无法完成,这时可以先用小比例的虚拟代理替换真实的对象,消除用户对服务器慢的感觉。
保护代理
保护代理即Protection Proxy,它用代理对象控制对原始对象的访问,常用于鉴权。
智能代理
智能引用即Smart Reference,它也是一种代理对象,主要用于调用目标对象时,代理附加一些额外的处理功能。例如,增加计算真实对象的引用次数的功能,这样当该对象没有被引用时,就可以自动释放它。
延迟加载
延迟加载,指为了提高系统的性能,延迟对目标的加载。
代理模式使用示例
静态代理实现
public class StaticProxy {public interface ILove {void makeGirlFriend();}// 真实对象public static class TargetLove implements ILove {@Overridepublic void makeGirlFriend() {System.out.println("交女朋友了");}}// 代理对象// 1. 交女朋友之前先做朋友// 2. 谈恋爱之后考虑结婚public static class ProxyLove implements ILove {private ILove love;public ProxyLove(ILove love) {this.love = love;}@Overridepublic void makeGirlFriend() {System.out.println("先做朋友吧,了解一下...");love.makeGirlFriend();System.out.println("对方很不错,考虑结婚...");}}public static void main(String[] args) {TargetLove targetLove = new TargetLove();ProxyLove proxyLove = new ProxyLove(targetLove);proxyLove.makeGirlFriend();}}
程序运行结果:
:::success
先做朋友吧,了解一下…
交女朋友了
对方很不错,考虑结婚…
:::
动态代理实现
public class DynamicProxy {
public interface ILove {
void makeGirlFriend();
}
// 真实对象
public static class TargetLove implements ILove {
@Override
public void makeGirlFriend() {
System.out.println("交女朋友了");
}
}
private static class LoveProxyHandler implements InvocationHandler {
private ILove love;
public LoveProxyHandler(ILove love) {
this.love = love;
}
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Exception {
System.out.println(o.getClass());
System.out.println(love.getClass());
System.out.println("先做朋友吧,了解一下...");
// 当前处于代理代理的回调中,这里要使用真实的对象,可以正常处理真实对象的意图
Object invoke = method.invoke(love, objects);
System.out.println("对方很不错,考虑结婚...");
return invoke;
}
}
public static void main(String[] args) {
ILove targetLove = new TargetLove();
/*
* 注意Proxy.newProxyInstance()方法接受三个参数:
*
* ClassLoader loader:指定当前目标对象使用的类加载器,获取加载器的方法是固定的
* Class<?>[] interfaces:指定目标对象实现的接口的类型,使用泛型方式确认类型
* InvocationHandler:指定动态处理器,执行目标对象的方法时,会触发事件处理器的方法
*/
ILove love = (ILove) Proxy.newProxyInstance(ILove.class.getClassLoader(), new Class[]{ILove.class},
new LoveProxyHandler(targetLove));
love.makeGirlFriend();
}
}
程序运行结果如下:
:::success
class com.sun.proxy.$Proxy0
class com.bujian.designpatterns.proxy.DynamicProxy$TargetLove
先做朋友吧,了解一下…
:::
:::success
交女朋友了
:::
:::success
对方很不错,考虑结婚…
:::
虽然相对于静态代理,动态代理大大减少了我们的开发任务,同时减少了对业务接口的依赖,降低了耦合度。但是还是有一点点小小的遗憾之处,那就是它始终无法摆脱仅支持interface代理的桎梏。 JDK实现动态代理需要实现类通过接口定义业务方法,对于没有接口的类,如何实现动态代理呢,这就需要CGLib了。CGLib采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。但因为采用的是继承,所以不能对final修饰的类进行代理。
与装饰器模式的区别
装饰器模式为了增强功能,而代理模式是为了加以控制
与适配器模式的区别
适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口
