1 模式动机
在某些情况下,一个客户不想或者不能直接引用一个对象,此时可以通过一个称之为“代理”的第三者来实现间接引用。代理对象可以在客户端和目标对象之间起到 中介的作用,并且可以通过代理对象去掉客户不能看到 的内容和服务或者添加客户需要的额外服务。
通过引入一个新的对象(如小图片和远程代理 对象)来实现对真实对象的操作或者将新的对象作为真实对象的一个替身,这种实现机制即为代理模式,通过引入代理对象来间接访问一 个对象,这就是代理模式的模式动机。
2 模式定义
代理模式(Proxy Pattern) :给某一个对象提供一个代理,并由代理对象控制对原对象的引用。代理模式的英 文叫做Proxy或Surrogate,它是一种对象结构型模式。
3 模式结构
代理模式包含如下角色:
- Subject: 抽象主题角色
- Proxy: 代理主题角色
- RealSubject: 真实主题角色
需要注意的有下面几点:
- 用户只关心接口功能,而不在乎谁提供了功能。上图中接口是 Subject。
- 接口真正实现者是上图的 RealSubject,但是它不与用户直接接触,而是通过代理。
- 代理就是上图中的 Proxy,由于它实现了 Subject 接口,所以它能够直接与用户接触。
- 用户调用 Proxy 的时候,Proxy 内部调用了 RealSubject。所以,Proxy 是中介者,它可以增强 RealSubject 操作。
4 时序图
5 代码分析
5.1 静态代理
模拟场景:我们平常去电影院看电影的时候,在电影开始的阶段是不是经常会放广告呢?电影是电影公司委托给影院进行播放的,但是影院可以在播放电影的时候,产生一些自己的经济收益,比如卖爆米花、可乐等,然后在影片开始结束时播放一些广告,现在用代码来进行模拟。
1). 抽象主题(接口)
首先得有一个接口,通用的接口是代理模式实现的基础。这个接口我们命名为Movie,代表电影这个主题。
package com.frank.test; public interface Movie { void play(); }
2). 被代理角色(目标类)与代理角色(代理类)
然后,我们要有一个真正的实现这个 Movie 接口的类和一个只是实现接口的代理类。
package com.frank.test; public class RealMovie implements Movie { @Override public void play() { // TODO Auto-generated method stub System.out.println("您正在观看电影 《肖申克的救赎》"); } }
这个表示真正的影片。它实现了 Movie 接口,play()方法调用时,影片就开始播放。那么代理类呢?
package com.frank.test; public class Cinema implements Movie { RealMovie movie; public Cinema(RealMovie movie) { super(); this.movie = movie; } @Override public void play() { guanggao(true); // 代理类的增强处理 movie.play(); // 代理类把具体业务委托给目标类,并没有直接实现 guanggao(false); // 代理类的增强处理 } public void guanggao(boolean isStart){ if ( isStart ) { System.out.println("电影马上开始了,爆米花、可乐、口香糖9.8折,快来买啊!"); } else { System.out.println("电影马上结束了,爆米花、可乐、口香糖9.8折,买回家吃吧!"); } } }
Cinema 就是代理对象,它有一个 play() 方法。不过调用 play() 方法时,它进行了一些相关利益的处理,那就是广告。也就是说,Cinema(代理类) 与 RealMovie(目标类) 都可以播放电影,但是除此之外,Cinema(代理类)还对“播放电影”这个行为进行进一步增强,即增加了额外的处理,同时不影响RealMovie(目标类)的实现。
3). 客户端
package com.frank.test; public class ProxyTest { public static void main(String[] args) { RealMovie realmovie = new RealMovie(); Movie movie = new Cinema(realmovie); movie.play(); } } /** Output 电影马上开始了,爆米花、可乐、口香糖9.8折,快来买啊! 您正在观看电影 《肖申克的救赎》 电影马上结束了,爆米花、可乐、口香糖9.8折,买回家吃吧! **/
现在可以看到,代理模式可以在不修改被代理对象的基础上(符合开闭原则),通过扩展代理类,进行一些功能的附加与增强。值得注意的是,代理类和被代理类应该共同实现一个接口,或者是共同继承某个类。如前所述,由于Cinema(代理类)是事先编写、编译好的,而不是在程序运行过程中动态生成的,因此这个例子是一个静态代理的应用。
5.2 动态代理
在上一节我们已经提到,动态代理可以在程序运行期间根据需要动态的创建代理类及其实例来完成具体的功能,下面我们结合具体实例来介绍JDK动态代理。
1). 抽象主题(接口)
同样地,首先得有一个接口,通用的接口是代理模式实现的基础。
package cn.inter; public interface Subject { public void doSomething(); }
2). 被代理角色(目标类)
然后,我们要有一个真正的实现这个 Subject 接口的类,以便代理。
package cn.impl; import cn.inter.Subject; public class RealSubject implements Subject { public void doSomething() { System.out.println("call doSomething()"); } }
3). 代理角色(代理类)与客户端
在动态代理中,代理类及其实例是程序自动生成的,因此我们不需要手动去创建代理类。在Java的动态代理机制中,InvocationHandler(Interface)接口和Proxy(Class)类是实现我们动态代理所必须用到的。事实上,Proxy通过使用InvocationHandler对象生成具体的代理代理对象,下面我们看一下对InvocationHandler接口的实现:
package cn.handler; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; /** * Title: InvocationHandler 的实现 * Description: 每个代理的实例都有一个与之关联的 InvocationHandler * 实现类,如果代理的方法被调用,那么代理便会通知和转发给内部的 InvocationHandler 实现类,由它调用invoke()去处理。 * * @author rico * @created 2017年7月3日 下午3:08:55 */ public class ProxyHandler implements InvocationHandler { private Object proxied; // 被代理对象 public ProxyHandler(Object proxied) { this.proxied = proxied; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 在转调具体目标对象之前,可以执行一些功能处理 System.out.println("前置增强处理: yoyoyo..."); // 转调具体目标对象的方法(三要素:实例对象 + 实例方法 + 实例方法的参数) Object obj = method.invoke(proxied, args); // 在转调具体目标对象之后,可以执行一些功能处理 System.out.println("后置增强处理:hahaha..."); return obj; } }
在实现了InvocationHandler接口后,我们就可以创建代理对象了。在Java的动态代理机制中,我们使用Proxy类的静态方法newProxyInstance创建代理对象,如下:
package cn.client; import java.lang.reflect.Proxy; import cn.handler.ProxyHandler; import cn.impl.RealSubject; import cn.inter.Subject; public class Test { public static void main(String args[]) { // 真实对象real Subject real = new RealSubject(); // 生成real的代理对象 Subject proxySubject = (Subject) Proxy.newProxyInstance( Subject.class.getClassLoader(), new Class[] { Subject.class }, new ProxyHandler(real)); proxySubject.doSomething(); System.out.println("代理对象的类型 : " + proxySubject.getClass().getName()); System.out.println("代理对象所在类的父类型 : " + proxySubject.getClass().getGenericSuperclass()); } } /** Output 前置增强处理: yoyoyo... call doSomething() 后置增强处理:hahaha... 代理对象的类型 : com.sun.proxy.$Proxy0 代理对象所在类的父类型 : class java.lang.reflect.Proxy **/
到此为止,我们给出了完整的基于JDK动态代理机制的代理模式的实现。我们从上面的实例中可以看到,代理对象proxySubject的类型为”com.sun.proxy.$Proxy0”,这恰好印证了proxySubject对象是一个代理对象。除此之外,我们还发现代理对象proxySubject所对应的类继承自java.lang.reflect.Proxy类,这也正是JDK动态代理机制无法实现对class的动态代理的原因:Java只允许单继承。
4). JDK中InvocationHandler接口与Proxy类
(1). InvocationHandler接口
InvocationHandler 是一个接口,官方文档解释说:每个代理的实例都有一个与之关联的 InvocationHandler 实现类,如果代理的方法被调用,那么代理便会通知和转发给内部的 InvocationHandler 实现类,由它决定处理。
public interface InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable; }
InvocationHandler中的invoke() 方法决定了怎么样处理代理传递过来的方法调用。
(2). Proxy类
JDK通过 Proxy 的静态方法 newProxyInstance 动态地创建代理,该方法在Java中的声明如下:
/** * @description * @author rico * @created 2017年7月3日 下午3:16:49 * @param loader 类加载器 * @param interfaces 目标类所实现的接口 * @param h InvocationHandler 实例 * @return */ public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
事实上,Proxy 动态产生的代理对象调用目标方法时,代理对象会调用 InvocationHandler 实现类,所以 InvocationHandler 是实际执行者。
五. Spring AOP 与 动态代理
AOP 专门用于处理系统中分布于各个模块(不同方法)中的交叉关注点的问题,在 Java EE 应用中,常常通过 AOP 来处理一些具有横切性质的系统级服务,如事务管理、安全检查、缓存、对象池管理等,AOP 已经成为一种非常常用的解决方案。
AOP机制是 Spring 所提供的核心功能之一,其既是Java动态代理机制的经典应用,也是动态AOP实现的代表。Spring AOP默认使用Java动态代理来创建AOP代理,具体通过以下几个步骤来完成:
Spring IOC 容器创建Bean(目标类对象);
Bean创建完成后,Bean后处理器(BeanPostProcessor)根据具体的切面逻辑及Bean本身使用Java动态代理技术生成代理对象;
应用程序使用上述生成的代理对象替代原对象来完成业务逻辑,从而达到增强处理的目的。
6.8. 优点
代理模式的优点
- 代理模式能够协调调用者和被调用者,在一定程度上降低了系 统的耦合度。
- 远程代理使得客户端可以访问在远程机器上的对象,远程机器 可能具有更好的计算性能与处理速度,可以快速响应并处理客户端请求。
- 虚拟代理通过使用一个小对象来代表一个大对象,可以减少系 统资源的消耗,对系统进行优化并提高运行速度。
- 保护代理可以控制对真实对象的使用权限。
6.10. 适用环境
根据代理模式的使用目的,常见的代理模式有以下几种类型:
- 远程(Remote)代理:为一个位于不同的地址空间的对象提供一个本地 的代理对象,这个不同的地址空间可以是在同一台主机中,也可是在 另一台主机中,远程代理又叫做大使(Ambassador)。
- 虚拟(Virtual)代理:如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。
- Copy-on-Write代理:它是虚拟代理的一种,把复制(克隆)操作延迟 到只有在客户端真正需要时才执行。一般来说,对象的深克隆是一个 开销较大的操作,Copy-on-Write代理可以让这个操作延迟,只有对象被用到的时候才被克隆。
- 保护(Protect or Access)代理:控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限。
- 缓冲(Cache)代理:为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果。
- 防火墙(Firewall)代理:保护目标不让恶意用户接近。
- 同步化(Synchronization)代理:使几个用户能够同时使用一个对象而没有冲突。
- 智能引用(Smart Reference)代理:当一个对象被引用时,提供一些额外的操作,如将此对象被调用的次数记录下来等。
6.11. 模式应用
EJB、Web Service等分布式技术都是代理模式的应用。在EJB中使用了RMI机制,远程服务器中的企业级Bean在本地有一个桩代理,客户端通过桩来调用远程对象中定义的方法,而无须直接与远程对象交互。在EJB的使用中需要提供一个公共的接口,客户端针对该接口进行编程,无须知道桩以及远程EJB的实现细节。
6.12. 模式扩展
几种常用的代理模式
- 图片代理:一个很常见的代理模式的应用实例就是对大图浏览的控制。
- 用户通过浏览器访问网页时先不加载真实的大图,而是通过代理对象的方法来进行处理,在代理对象的方法中,先使用一个线程向客户端浏览器加载一个小图片,然后在后台使用另一个线程来调用大图片的加载方法将大图片加载到客户端。当需要浏览大图片时,再将大图片在新网页中显示。如果用户在浏览大图时加载工作还没有完成,可以再启动一个线程来显示相应的提示信息。通过代理技术结合多线程编程将真实图片的加载放到后台来操作,不影响前台图片的浏览。
- 远程代理:远程代理可以将网络的细节隐藏起来,使得客户端不必考虑网络的存在。客户完全可以认为被代理的远程业务对象是局域的而不是远程的,而远程代理对象承担了大部分的网络通信工作。
- 虚拟代理:当一个对象的加载十分耗费资源的时候,虚拟代理的优势就非常明显地体现出来了。虚拟代理模式是一种内存节省技术,那些占用大量内存或处理复杂的对象将推迟到使用它的时候才创建。
-在应用程序启动的时候,可以用代理对象代替真实对象初始化,节省了内存的占用,并大大加速了系统的启动时间。
动态代理
- 动态代理是一种较为高级的代理模式,它的典型应用就是Spring AOP。
- 在传统的代理模式中,客户端通过Proxy调用RealSubject类的request()方法,同时还在代理类中封装了其他方法(如preRequest()和postRequest()),可以处理一些其他问题。
- 如果按照这种方法使用代理模式,那么真实主题角色必须是事先已经存在的,并将其作为代理对象的内部成员属性。如果一个真实主题角色必须对应一个代理主题角色,这将导致系统中的类个数急剧增加,因此需要想办法减少系统中类的个数,此外,如何在事先不知道真实主题角色的情况下使用代理主题角色,这都是动态代理需要解决的问题。
6.13. 总结
在代理模式中,要求给某一个对象提供一个代理,并由代理对象控制对原对象的引用。代理模式的英文叫做Proxy或Surrogate,它是一种对象结构型模式。 - 代理模式包含三个角色:抽象主题角色声明了真实主题和代理主题的共同接口;代理主题角色内部包含对真实主题的引用,从而可以在任何时候操作真实主题对象;真实主题角色定义了代理角色所代表的真实对象,在真实主题角色中实现了真实的业务操作,客户端可以通过代理主题角色间接调用真实主题角色中定义的方法。 - 代理模式的优点在于能够协调调用者和被调用者,在一定程度上降低了系统的耦合度;其缺点在于由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢,并且实现代理模式需要额外的工作,有些代理模式的实现非常复杂。远程代理为一个位于不同的地址空间的对象提供一个本地的代表对象,它使得客户端可以访问在远程机器上的对象,远程机器可能具有更好的计算性能与处理速度,可以快速响应并处理客户端请求。- 如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建,这个小对象称为虚拟代理。虚拟代理通过使用一个小对象来代表一个大对象,可以减少系统资源的消耗,对系统进行优化并提高运行速度。 - 保护代理可以控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限。