在某些情况下,一个客户不想或者不能直接引用一个对象,此时可以通过一个称之为“代理”的第三者来实现 间接引用。代理对象可以在客户端和目标对象之间起到中介的作用,并且可以通过代理对象去掉客户不能看到 的内容和服务或者添加客户需要的额外服务。
通过引入一个新的对象(如小图片和远程代理 对象)来实现对真实对象的操作或者将新的对 象作为真实对象的一个替身,这种实现机制即 为代理模式,通过引入代理对象来间接访问一 个对象,这就是代理模式的模式动机。
代理模式(Proxy Pattern) :给某一个对象提供一个代理,并由代理对象控制对原对象的引用。代理模式的英 文叫做Proxy或Surrogate,它是一种对象结构型模式。
1. 静态代理
所谓 静态代理,就是在编译阶段就生成代理类来完成对代理对象的一系列操作。下面是代理模式的结构类图:
1.1 模式中的角色
抽象实体(Subject):定义了真实实体(RealSubject)和代理(Proxy)的公共接口,这样就在任何时候使用真实实体(RealSubject)的地方使用代理(Proxy)。
代理(Proxy):保存一个引用使得代理可以直接访问真实实体,并提供一个与Subject的接口相同的接口,这样代理就可以代替真实实体。
真实实体(RealSubject):定义Proxy所代表的真实实体。
1.2 代理模式的类图
1.3 代理模式的实现思路
- 代理对象和目标对象均实现同一个行为接口。
- 代理类和目标类分别具体实现接口逻辑。
- 在代理类的构造函数中实例化一个目标对象。
- 在代理类中调用目标对象的行为接口。
- 客户端想要调用目标对象的行为接口,只能通过代理类来操作。
1.4 静态代理的实例
下面以一个延迟加载的例子来说明一下静态代理。我们在启动某个服务系统时,加载某一个类时可能会耗费很长时间。为了获取更好的性能,在启动系统的时候,我们往往不去初始化这个复杂的类,取而代之的是去初始化其代理类。这样将耗费资源多的方法使用代理进行分离,可以加快系统的启动速度,减少用户等待的时间。
定义一个主题接口
public interface Subject {
public void sayHello();
public void sayGoodBye();
}
定义一个目标类, 并实现主题接口
public class RealSubject implements Subject {
public void sayHello() {
System.out.println("Hello World");
}
public void sayGoodBye() {
System.out.println("GoodBye World");
}
}
定义一个代理类,来代理目标对象
public class StaticProxy implements Subject {
Private RealSubject realSubject = null;
public StaticProxy() {}
public void sayHello() {
//用到时候才加载,懒加载
if(realSubject == null) {
realSubject = new RealSubject();
}
realSubject.sayHello();
}
//sayGoodbye方法同理
...
}
定义一个客户端
public class Client {
public static void main(String [] args) {
StaticProxy sp = new StaticProxy();
sp.sayHello();
sp.sayGoodBye();
}
}
以上就是静态代理的一个简单测试例子。感觉可能没有实际用途。然而并非如此。使用代理我们还可以将目标对象的方法进行改造,比如数据库连接池中创建了一系列连接,为了保证不频繁的打开连接,这些连接是几乎不会关闭的。然而我们编程总有习惯去将打开的 Connection 去 close 。 这样我们就可以利用代理模式来重新代理 Connection 接口中的 close 方法,改变为回收到数据库连接池中而不是真正的执行 Connection.close 方法。其他的例子还有很多,具体需要自己体会
2. 动态代理
动态代理是指在运行时动态生成代理类。即,代理类的字节码将在运行时生成并载入当前代理的 ClassLoader。与静态处理类相比,动态类有诸多好处。
2.1 优点
不需要为真实主题写一个形式上完全一样的封装类,假如主题接口中的方法很多,为每一个接口写一个代理方法也很麻烦。如果接口有变动,则真实主题和代理类都要修改,不利于系统维护;
使用一些动态代理的生成方法甚至可以在运行时制定代理类的执行逻辑,从而大大提升系统的灵活性。
2.2 方式
生成动态代理的方法有很多: JDK中自带动态代理,CGlib, javassist等。这些方法各有优缺点。本文主要探究JDK中的动态代理的使用和源码分析。
2.3 动态代理实例
下面用一个实例讲解一下JDK中动态代理的用法:
public class dynamicProxy implements InvocationHandler {
private RealSubject = null;
public Object invoke(Object proxy, Method method, Object[] args){
if(RealSubject == null) {
RealSubject = new RealSubject();
}
method.invoke(RealSubject, args);
return RealSubject;
}
}
客户端代码实例
public class Client {
public static void main(Strings[] args) {
Subject subject = (Subject)Proxy.newInstance(ClassLoader.getSystemLoader(), RealSubject.class.getInterfaces(), new DynamicProxy());
Subject.sayHello();
Subject.sayGoodBye();
}
}
从上面的代码可以看出,要利用JDK中的动态代理。利用静态方法Proxy.newInstance(ClassLoader, Interfaces[], InvokeHandler)可以创建一个动态代理类。 newInstance方法有三个参数,分别表示类加载器,一个希望该代理类实现的接口列表,以及实现InvokeHandler接口的实例。 动态代理将每个方法的执行过程则交给了Invoke方法处理。
3. 优点
代理模式能够协调调用者和被调用者,在一定程度上降低了系 统的耦合度。
远程代理使得客户端可以访问在远程机器上的对象,远程机器 可能具有更好的计算性能与处理速度,可以快速响应并处理客户端请求。
虚拟代理通过使用一个小对象来代表一个大对象,可以减少系 统资源的消耗,对系统进行优化并提高运行速度。
保护代理可以控制对真实对象的使用权限。
4. 缺点
由于在客户端和真实主题之间增加了代理对象,因此 有些类型的代理模式可能会造成请求的处理速度变慢。
实现代理模式需要额外的工作,有些代理模式的实现 非常复杂。
5. 适用环境
远程(Remote)代理:为一个位于不同的地址空间的对象提供一个本地 的代理对象,这个不同的地址空间可以是在同一台主机中,也可是在 另一台主机中,远程代理又叫做大使(Ambassador)。
虚拟(Virtual)代理:如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。
Copy-on-Write代理:它是虚拟代理的一种,把复制(克隆)操作延迟 到只有在客户端真正需要时才执行。一般来说,对象的深克隆是一个 开销较大的操作,Copy-on-Write代理可以让这个操作延迟,只有对象被用到的时候才被克隆。
保护(Protect or Access)代理:控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限。
缓冲(Cache)代理:为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。
防火墙(Firewall)代理:保护目标不让恶意用户接近。
同步化(Synchronization)代理:使几个用户能够同时使用一个对象而没有冲突。
智能引用(Smart Reference)代理:当一个对象被引用时,提供一些额外的操作,如将此对象被调用的次数记录下来等。
参考文章:
1.设计模式学习-代理模式
2.设计模式经典 - 代理模式