代理模式(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注:静态代理,如果接口增加或删除一个方法,该接口的所有实现类和代理类都需要改变,灵活性和扩展性低。
动态代理,由于动态代理是在运行时动态生成的,所以对接口的改变可以很好的适应。这主要得力于动态代理将接口中声明的所有方法都转移到一个集中的方法中处理,而不是像静态代理那样对接口的每一个方法进行中转。缺点是只能代理接口,且代理类需要实现特定接口(invocationHandler接口),覆盖其invoke方法。
五、模式优缺点
优点:
1、远程代理可以隐藏一个对象存在于不同地址空间的事实,对客户端透明,降低客户端操作的复杂性。
2、 虚拟代理可以根据需要进行最优化,例如延迟对象创建。
3、缓冲代理通过缓存和共享操作执行结果,缩短执行时间,优化系统性能。
4、保护代理和智能指针都可以在访问对象时添加附加的业务逻辑,比如权限判断,引用计数。
5、总体来说,代理模式在访问对象时引入一定程度的间接性,这种间接性很好的起到隔离作用,降低系统的耦合度。
缺点:
1、 实现代理模式需要额外的工作,而且有些代理模式的实现过程较为复杂,例如远程代理。