代理模式(PROXY),别名Surrogate(代理),通过为其他对象提供一种代理以控制对这个对象的访问,属于对象结构型模式。软件开发中,经常会出现特殊对象,创建这些对象时开销很大,比如文档中的图片,数据库连接等,我们应该根据需要对这些对象进行创建,当图片可见时创建图片对象,当数据库连接真的需要执行时创建连接。通过创建代理,我们可以控制对象的创建,访问时机。创建图片代理,将其作为占位符插入到文档中,当图片可见时,再通过图片代理加载图片(图片的下载获取操作可以放到单独的线程中处理,代理只需将图片加载并显示即可。当然,代理也可以把获取图片和显示图片都做了),以优化程序的响应速度。通过代理还可以在运行时环境中为对象方法添加业务逻辑,比如日志记录,时间消耗统计。Spring AOP,Hibernate等流行框架大量使用代理增强现有对象的能力,以获取好的性能,灵活的扩展性。
一、模式分类
根据功能和实现效果的不同,代理模式被划分为很多种,常用的如下:
1、远程代理,又称"大使"(Ambassador),为一个对象在不同的地址空间提供局部代表。不同的地址空间可以是局域网中的不同主机或互联网中的不同主机。java中的远程方法调用就是通过远程代理实现的。
2、虚代理,根据需要创建开销很大的对象,将对象的创建尽可能延迟。文档中的图片代理就是虚代理,先提供代理作为占位符,等图片显示时再创建对象。
3、保护代理,控制对原始对象的访问,一般用于为对象添加访问权限的判断。
4、缓冲代理,为操作结果提供临时的缓存存储空间,以便后续共享。
5、智能指针,取代简单的指针,在访问对象时执行一些附加操作。比如,对指向对象添加引用计数,当该对象没有引用时自动释放它。当持久化对象第一次被引用时,将其装入内存,Hibernate等ORM框架经常这样做。在访问一个实际对象前,检查是否已锁定它,以确保其他对象不能改变它。
二、使用场景
1、当客户端对象需要访问远程主机中的对象时可以使用远程代理,比如远程方法调用。
2、 当需要用一个消耗资源较少的对象来代表一个消耗资源较多的对象,从而降低系统开销、缩短运行时间时可以使用虚拟代理。
3、 当需要为某一个被频繁访问的操作结果提供一个临时存储空间,以供多个客户端共享访问这些结果时可以使用缓冲代理。通过使用缓冲代理,系统无须在 客户端每一次访问时都重新执行操作,只需直接从临时缓冲区获取操作结果即可。
4、 当需要控制对一个对象的访问,为不同用户提供不同级别的访问权限时可以使用保护代理。
5、 当需要为一个对象的访问(引用)提供一些额外的操作时可以使用智能指针。
三、UML图
注:适配器模式为它所适配的对象提供一个不同的接口,而代理模式则提供与它的实体对象相同的接口。虽然装饰模式和适配器一样,都提供和实体一致的接口,但它们的目的不一样。装饰模式侧重为对象添加一个或多个功能,而代理则侧重控制对对象的访问。
四、Java实现
1、静态代理
package study.patterns.proxy; /** *代理模式:特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。 *代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。 *按照代理类的创建时期,代理类可分为两种: *1、静态代理类:由程序员创建或由特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件已经存在,即代理类和委托类之间的关系已确认。 *2、动态代理类:在程序运行时,运用反射机制动态生成。 * @author qbg */ public class ProxyPattern { public static void main(String[] args) { /** * 这些代码是事先编写好的,在程序运行前.class文件已经存在,所以属于静态代理. */ ISubject sub = new RealSubject(); ISubject proxy = new Proxy(sub); proxy.operator(); } } /** * 代理接口,要代理业务的抽象 */ interface ISubject{ public void operator(); } /** * 委托类,真正的业务处理 */ class RealSubject implements ISubject{ @Override public void operator() { System.out.println("do something...."); } } /** * 代理类:实现代理接口,实现额外操作 * 实现方式和装饰模式类似,但两者目的不同. */ class Proxy implements ISubject{ private ISubject sub; public Proxy(ISubject sub){ this.sub = sub; } @Override public void operator() { System.out.println("before operator....");//预处理 sub.operator();//调用具体主题方法 System.out.println("after operator....");//事后处理 } }运行结果:
before operator.... do something.... after operator....
2、动态代理
package study.patterns.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import javax.security.auth.Subject; /** *代理模式:特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。 *代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。 *按照代理类的创建时期,代理类可分为两种: *1、静态代理类:由程序员创建或由特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件已经存在,即代理类和委托类之间的关系已确认。 *2、动态代理:动态代理类的源码是在程序运行期间由JVM根据反射等机制动态的生成,所以不存在代理类的字节码文件。代理类和委托类的关系是在程序运行时确定。 * @author qbg */ public class ProxyPattern { /** * 动态代理实现步骤: * a. 实现InvocationHandler接口创建自己的调用处理器 . * b. 给Proxy类提供ClassLoader和代理接口类型数组创建动态代理类 . * c. 以调用处理器类型为参数,利用反射机制得到动态代理类的构造函数 . * d. 以调用处理器对象为参数,利用动态代理类的构造函数创建动态代理类对象 */ public static void main(String[] args) { ISubject proxy = DynamicProxyFactory.getInstance(); proxy.operator(); System.out.println("proxy class:"+proxy.getClass().getName()); } } /** * 代理接口,要代理业务的抽象 */ interface ISubject{ public void operator(); } /** * 委托类,真正的业务处理 */ class RealSubject implements ISubject{ @Override public void operator() { System.out.println("do something...."); } } /** * 动态代理类对应的调用处理程序类 */ class SubjectInvocationHandler implements InvocationHandler { // 代理类持有一个委托类的对象引用 private Object delegate; public SubjectInvocationHandler(Object delegate) { this.delegate = delegate; } /** * 利用反射机制将请求分派给委托类处理。 Method的invoke返回Object对象作为方法执行结果(此处没有处理)。 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("before operator....."); method.invoke(delegate, args); System.out.println("after operator...."); return null; } } /** * 生成动态代理对象的工厂. */ class DynamicProxyFactory { /** * 客户类可以通过调用此工厂方法获得代理对象. * 对客户类来说,其并不知道返回的是代理类对象还是委托类对象。 */ public static ISubject getInstance() { ISubject delegate = new RealSubject(); InvocationHandler handler = new SubjectInvocationHandler(delegate); ISubject proxy = null; proxy = (ISubject) Proxy .newProxyInstance(delegate.getClass().getClassLoader(), delegate.getClass().getInterfaces(), handler); return proxy; } }运行结果:
before operator..... do something.... after operator.... proxy class:study.patterns.proxy.$Proxy0注:静态代理,如果接口增加或删除一个方法,该接口的所有实现类和代理类都需要改变,灵活性和扩展性低。
五、模式优缺点
优点:
1、远程代理可以隐藏一个对象存在于不同地址空间的事实,对客户端透明,降低客户端操作的复杂性。
2、 虚拟代理可以根据需要进行最优化,例如延迟对象创建。
3、缓冲代理通过缓存和共享操作执行结果,缩短执行时间,优化系统性能。
4、保护代理和智能指针都可以在访问对象时添加附加的业务逻辑,比如权限判断,引用计数。
5、总体来说,代理模式在访问对象时引入一定程度的间接性,这种间接性很好的起到隔离作用,降低系统的耦合度。
缺点:
1、 实现代理模式需要额外的工作,而且有些代理模式的实现过程较为复杂,例如远程代理。