设计模式之代理模式
1 概述
代理模式(Proxy)就是为一个对象创建一个替身,用来控制对当前对象的访问。目的就是为了在不直接操作对象的前提下对对象进行访问。
根据代理类和被代理类的关系来区分的话,可以分为静态代理和动态代理。
(1)静态代理:在运行之前,就确定好代理类、被代理类之间的关系。
(2)动态代理:在运行时动态的创建一个代理类,实现一个或多个接口,将方法的调用转发到指定的类。
根据不同的功用性,可以分为远程代理、虚拟代理、保护代理、缓存代理、写入代理等。
(1)远程代理:为一个位于不同的JVM堆中的对象提供一个本地的代理对象,Java有自带的RMI方式来实现;
(2)虚拟代理:为创建开销大的对象提供代理,当对象在创建前和创建中时,由虚拟代理来作为对象的替身,对象创建后,代理就会将请求直接转给对象。
(3)保护代理: 主要用于当对象应该有不同的访问权限的时。
2 示例
2.1 静态代理
在智能手机如此火热的今天,动手写个手机遥控关闭电脑的小软件也不是难事,今天就以它作为例子。手机代理电脑关机键,完成关机功能。
首先来个关电脑的接口:
1 package org.scott.proxy; 2 /** 3 * @author Scott 4 * @date 2013-11-27 5 * @description 6 */ 7 public interface Operate { 8 public void powerOff(); 9 }
然后是实际关电脑的执行类:
1 package org.scott.proxy; 2 /** 3 * @author Scott 4 * @date 2013-11-27 5 * @description 6 */ 7 public class PCPowerOff implements Operate{ 8 9 @Override 10 public void powerOff() { 11 System.out.println("The pc is power off."); 12 } 13 }
现在就利用静态代理模式,来创建个手机关闭电脑的代理类:
1 package org.scott.proxystatic; 2 3 import org.scott.proxy.Operate; 4 import org.scott.proxy.PCPowerOff; 5 6 /** 7 * @author Scott 8 * @date 2013-11-27 9 * @description 10 */ 11 public class PhoneProxy implements Operate{ 12 13 private PCPowerOff pcPowerOff; 14 public PhoneProxy(){ 15 this.pcPowerOff = new PCPowerOff(); 16 } 17 18 @Override 19 public void powerOff() { 20 prePowerOff(); 21 this.pcPowerOff.powerOff(); 22 afterPowerOff(); 23 } 24 25 public void prePowerOff(){ 26 System.out.println("Before power off the computer by static proxy, check the email list."); 27 } 28 29 public void afterPowerOff(){ 30 System.out.println("After power off the computer by static proxy, say good-night."); 31 } 32 }
来个测试类:
1 package org.scott.proxystatic; 2 3 import org.scott.proxy.Operate; 4 5 /** 6 * @author Scott 7 * @date 2013-11-27 8 * @description 9 */ 10 public class ProxyStaticTest { 11 12 public static void main(String[] args) { 13 Operate operate = new PhoneProxy(); 14 operate.powerOff(); 15 } 16 17 }
执行结果:
Before power off the computer by static proxy, check the email list. The pc is power off. After power off the computer by static proxy, say good-night.
静态代理有两个弱点(源自网络总结):
1)代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理,静态代理在程序规模稍大时就无法胜任了。
2)如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
于是乎,到了“动态代理”上场的时候了。
2.2 动态代理
目前,实现动态代理,主要是靠反射,并依赖于JDK中的java.lang.reflect.Proxy类、java.lang.ClassLoader类以及java.lang.reflect.InvocationHandler接口。
2.2.1 java.lang.reflect.Proxy
这个类提供了一组静态方法来为一组接口动态地通过反射生成代理类及其对象。
常用的方法有以下四个:
//用于获取指定代理对象所关联的调用处理器 static InvocationHandler getInvocationHandler(Object proxy) //用于获取关联于指定类装载器和一组接口的动态代理类的类对象 static Class getProxyClass(ClassLoader loader, Class[] interfaces) //用于判断指定类对象是否是一个动态代理类 static boolean isProxyClass(Class cl) //用于为指定类装载器、一组接口及调用处理器生成动态代理类实例 static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)
2.2.2 java.lang.ClassLoader
类装载器类负责加载类,将类的字节码装载到JVM中并为其定义类对象。Proxy静态方法生成动态代理类同样需要通过类装载器来进行装载才能使用,并且字节码是在程序运行过程中动态产生的。
2.2.3 java.lang.reflect.InvocationHandler
InvocationHandler实现了代理的行为。每个代理实例都具有一个关联的调用处理程序,对代理实例调用方法时,将对方法调用进行编码并将其指派到它的调用处理程序的 invoke
方法。每次生成动态代理类对象时都要指定一个对应的调用处理器对象。
Object invoke(Object proxy, Method method, Object[] args) throws Throwable
参数:
(1)proxy:在其上调用方法的代理对象;
(2)method:对应于在代理实例上调用的接口方法的 Method 实例。Method 对象的声明类将是在其中声明方法的接口,该接口可以是代理类赖以继承方法的代理接口的超接口;
(3)args:包含传入代理实例上方法调用的参数值的对象数组,如果接口方法不使用参数,则为 null。基本类型的参数被包装在适当基本包装器类(如 java.lang.Integer 或 java.lang.Boolean)的实例中。
返回:
从代理实例的方法调用返回的值。
如果接口方法的声明返回类型是基本类型,则此方法返回的值一定是相应基本包装对象类的实例;否则,它一定是可分配到声明返回类型的类型。如果此方法返回的值为null并且接口方法的返回类型是基本类型,则代理实例上的方法调用将抛出 NullPointerException。否则,如果此方法返回的值与上述接口方法的声明返回类型不兼容,则代理实例上的方法调用将抛出 ClassCastException。
2.2.4 动态代理实现步骤
不要被上面列出的三个怪东西搞晕了,下面列一列由牛人们给出的实现动态代理模式的步骤:
(1)实现InvocationHandler接口,创建自己的调用处理器
(2)给Proxy类提供ClassLoader和代理接口类型数组创建动态代理类
(3)以调用处理器类型为参数,利用反射机制得到动态代理类的构造函数
(4)以调用处理器对象为参数,利用动态代理类的构造函数创建动态代理类对象
2.2.5 Code
好吧,来段代码来说明上面一连串的文字描述,例子还是上面的利用手机远程关闭电脑。
还是利用那个Operate接口和被代理类PCPowerOff。下面才是动态代理所特有的。
自定义的Invocation处理类:
1 package org.scott.proxydynamic; 2 3 import java.lang.reflect.InvocationHandler; 4 import java.lang.reflect.Method; 5 6 /** 7 * @author Scott 8 * @date 2013-11-27 9 * @description 10 */ 11 public class MyInvocationHandler implements InvocationHandler { 12 private Object obj; 13 14 public MyInvocationHandler(Object obj){ 15 this.obj = obj; 16 } 17 18 @Override 19 public Object invoke(Object proxy, Method method, Object[] args) 20 throws Throwable { 21 prePowerOff(); 22 23 //通过反射获取 24 method.invoke(obj, args); 25 26 afterPowerOff(); 27 28 return null; 29 } 30 31 public void prePowerOff(){ 32 System.out.println("Before power off the computer by dynamic proxy, check the email list."); 33 } 34 35 public void afterPowerOff(){ 36 System.out.println("After power off the computer by dynamic proxy, say good-night."); 37 } 38 }
客户端也很重要,再写个测试类:
1 package org.scott.proxydynamic; 2 3 import java.lang.reflect.InvocationHandler; 4 import java.lang.reflect.Proxy; 5 6 import org.scott.proxy.Operate; 7 import org.scott.proxy.PCPowerOff; 8 9 /** 10 * @author Scott 11 * @date 2013-11-27 12 * @description 13 */ 14 public class ProxyDynamicTest { 15 16 @SuppressWarnings("rawtypes") 17 public static void main(String[] args) { 18 Operate pcPowerOff = new PCPowerOff(); 19 InvocationHandler invocation = new MyInvocationHandler(pcPowerOff); 20 Class clazz = pcPowerOff.getClass(); 21 22 Operate proxy = (Operate) Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), invocation); 23 proxy.powerOff(); 24 } 25 26 }
运行结果:
Before power off the computer by dynamic proxy, check the email list.
The pc is power off.
After power off the computer by dynamic proxy, say good-night.
3 小结
是不是觉得代理模式,特别是静态代理那个例子和适配器有点类似?来比较一下。
(1)和适配器模式Adapter比较
适配器Adapter 为它所适配的对象提供了一个不同的接口。相反,代理提供了与它的被代理实体相同的接口。然而,用于访问保护的代理可能会拒绝执行实体会执行的操作,因此,它的接口实际上可能只是实体接口的一个子集。
(2)和装饰器模式Decorator比较
尽管Decorator的实现部分与代理相似,但 Decorator的目的不一样。Decorator为对象添加一个或多个功能,而代理则控制对对象的访问。
代理模式,尤其是动态代理模式,应用范围非常广泛,最典型的例子就是HDFS中的RPC实现,利用动态代理加上Protobuf,实现NameNode之间、NameNode和DataNode之间、NameNode和客户端之间、DataNode和DataNode之间、DataNode和客户端之间的通信。之前写了个HDFS的RPC分析,请见: