代理机制及其特点
首先让我们来了解一下如何使用 Java 动态代理。具体有如下四步骤:
- 通过实现 InvocationHandler 接口创建自己的调用处理器;
- 通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类;
- 通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;
- 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。
清单 3. 动态代理对象创建过程
// InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发 // 其内部通常包含指向委托类实例的引用,用于真正执行分派转发过来的方法调用 InvocationHandler handler = new InvocationHandlerImpl(..); // 通过 Proxy 为包括 Interface 接口在内的一组接口动态创建代理类的类对象 Class clazz = Proxy.getProxyClass(classLoader, new Class[] { Interface.class, ... }); // 通过反射从生成的类对象获得构造函数对象 Constructor constructor = clazz.getConstructor(new Class[] { InvocationHandler.class }); // 通过构造函数对象创建动态代理类实例 Interface Proxy = (Interface)constructor.newInstance(new Object[] { handler });
实际使用过程更加简单,因为 Proxy 的静态方法 newProxyInstance 已经为我们封装了步骤 2 到步骤 4 的过程,所以简化后的过程如下
清单 4. 简化的动态代理对象创建过程
// InvocationHandlerImpl 实现了 InvocationHandler 接口,并能实现方法调用从代理类到委托类的分派转发 InvocationHandler handler = new InvocationHandlerImpl(..); // 通过 Proxy 直接创建动态代理类实例 Interface proxy = (Interface)Proxy.newProxyInstance( classLoader, new Class[] { Interface.class }, handler );
机制和特点都介绍过了,接下来让我们通过源代码来了解一下 Proxy 到底是如何实现的。
首先记住 Proxy 的几个重要的静态变量:
清单 5. Proxy 的重要静态变量
// 映射表:用于维护类装载器对象到其对应的代理类缓存 private static Map loaderToCache = new WeakHashMap(); // 标记:用于标记一个动态代理类正在被创建中 private static Object pendingGenerationMarker = new Object(); // 同步表:记录已经被创建的动态代理类类型,主要被方法 isProxyClass 进行相关的判断 private static Map proxyClasses = Collections.synchronizedMap(new WeakHashMap()); // 关联的调用处理器引用 protected InvocationHandler h;
然后,来看一下 Proxy 的构造方法:
清单 6. Proxy 构造方法
// 由于 Proxy 内部从不直接调用构造函数,所以 private 类型意味着禁止任何调用 private Proxy() {} // 由于 Proxy 内部从不直接调用构造函数,所以 protected 意味着只有子类可以调用 protected Proxy(InvocationHandler h) {this.h = h;}
接着,可以快速浏览一下 newProxyInstance 方法,因为其相当简单:
清单 7. Proxy 静态方法 newProxyInstance
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException { // 检查 h 不为空,否则抛异常 if (h == null) { throw new NullPointerException(); } // 获得与制定类装载器和一组接口相关的代理类类型对象 Class cl = getProxyClass(loader, interfaces); // 通过反射获取构造函数对象并生成代理类实例 try { Constructor cons = cl.getConstructor(constructorParams); return (Object) cons.newInstance(new Object[] { h }); } catch (NoSuchMethodException e) { throw new InternalError(e.toString()); } catch (IllegalAccessException e) { throw new InternalError(e.toString()); } catch (InstantiationException e) { throw new InternalError(e.toString()); } catch (InvocationTargetException e) { throw new InternalError(e.toString()); } }
由此可见,动态代理真正的关键是在 getProxyClass 方法,该方法负责为一组接口动态地生成代理类类型对象。在该方法内部,您将能看到 Proxy 内的各路英雄(静态变量)悉数登场。有点迫不及待了么?那就让我们一起走进 Proxy 最最神秘的殿堂去欣赏一番吧。该方法总共可以分为四个步骤:
- 对这组接口进行一定程度的安全检查,包括检查接口类对象是否对类装载器可见并且与类装载器所能识别的接口类对象是完全相同的,还会检查确保是 interface 类型而不是 class 类型。这个步骤通过一个循环来完成,检查通过后将会得到一个包含所有接口名称的字符串数组,记为
String[] interfaceNames
。总体上这部分实现比较直观,所以略去大部分代码,仅保留留如何判断某类或接口是否对特定类装载器可见的相关代码。清单 8. 通过 Class.forName 方法判接口的可见性
try { // 指定接口名字、类装载器对象,同时制定 initializeBoolean 为 false 表示无须初始化类 // 如果方法返回正常这表示可见,否则会抛出 ClassNotFoundException 异常表示不可见 interfaceClass = Class.forName(interfaceName, false, loader); } catch (ClassNotFoundException e) { }
- 从 loaderToCache 映射表中获取以类装载器对象为关键字所对应的缓存表,如果不存在就创建一个新的缓存表并更新到 loaderToCache。缓存表是一个 HashMap 实例,正常情况下它将存放键值对(接口名字列表,动态生成的代理类的类对象引用)。当代理类正在被创建时它会临时保存(接口名字列表,pendingGenerationMarker)。标记 pendingGenerationMarke 的作用是通知后续的同类请求(接口数组相同且组内接口排列顺序也相同)代理类正在被创建,请保持等待直至创建完成。
清单 9. 缓存表的使用
do { // 以接口名字列表作为关键字获得对应 cache 值 Object value = cache.get(key); if (value instanceof Reference) { proxyClass = (Class) ((Reference) value).get(); } if (proxyClass != null) { // 如果已经创建,直接返回 return proxyClass; } else if (value == pendingGenerationMarker) { // 代理类正在被创建,保持等待 try { cache.wait(); } catch (InterruptedException e) { } // 等待被唤醒,继续循环并通过二次检查以确保创建完成,否则重新等待 continue; } else { // 标记代理类正在被创建 cache.put(key, pendingGenerationMarker); // break 跳出循环已进入创建过程 break; } while (true);
- 动态创建代理类的类对象。首先是确定代理类所在的包,其原则如前所述,如果都为 public 接口,则包名为空字符串表示顶层包;如果所有非 public 接口都在同一个包,则包名与这些接口的包名相同;如果有多个非 public 接口且不同包,则抛异常终止代理类的生成。确定了包后,就开始生成代理类的类名,同样如前所述按格式“$ProxyN”生成。类名也确定了,接下来就是见证奇迹的发生 —— 动态生成代理类:
清单 10. 动态生成代理类
// 动态地生成代理类的字节码数组 byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces); try { // 动态地定义新生成的代理类 proxyClass = defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length); } catch (ClassFormatError e) { throw new IllegalArgumentException(e.toString()); } // 把生成的代理类的类对象记录进 proxyClasses 表 proxyClasses.put(proxyClass, null);
由此可见,所有的代码生成的工作都由神秘的 ProxyGenerator 所完成了,当你尝试去探索这个类时,你所能获得的信息仅仅是它位于并未公开的 sun.misc 包,有若干常量、变量和方法以完成这个神奇的代码生成的过程,但是 sun 并没有提供源代码以供研读。至于动态类的定义,则由 Proxy 的 native 静态方法 defineClass0 执行。
- 代码生成过程进入结尾部分,根据结果更新缓存表,如果成功则将代理类的类对象引用更新进缓存表,否则清楚缓存表中对应关键值,最后唤醒所有可能的正在等待的线程。
走完了以上四个步骤后,至此,所有的代理类生成细节都已介绍完毕,剩下的静态方法如 getInvocationHandler 和 isProxyClass 就显得如此的直观,只需通过查询相关变量就可以完成,所以对其的代 码分析就省略了。
package com.hpf.proxy;
public interface People {
public void sayHello();
}
package com.hpf.proxy;
public class Student implements People{
@Override
public void sayHello() {
System. out.println( "student say hello");
}
}
package com.hpf.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class MyInvocationHandler implements InvocationHandler{
private Object object; //被代理的对象
public MyInvocationHandler(Object object) {
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System. out.println( "do before");
method.invoke( object, args);//调用原始对象的方法
System. out.println( "do after");
return null;
}
}
package com.hpf.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import org.junit.Test;
public class MyTestProxy {
@Test
public void testProxy(){
People people1= new Student();
InvocationHandler iHandler = new MyInvocationHandler(people1);
People people = (People) Proxy.newProxyInstance(people1.getClass().getClassLoader(),
new Class[]{People. class}, iHandler);
people.sayHello();//调用代理对象的方法,将会调用调用处理器的invoke方法
}
}
美中不足
有很多条理由,人们可以否定对 class 代理的必要性,但是同样有一些理由,相信支持 class 动态代理会更美好。接口和类的划分,本就不是很明显,只是到了 Java 中才变得如此的细化。如果只从方法的声明及是否被定义来考量,有一种两者的混合体,它的名字叫抽象类。实现对抽象类的动态代理,相信也有其内在的价值。此外,还有一些历史遗留的类,它们将因为没有实现任何接口而从此与动态代理永世无缘。如此种种,不得不说是一个小小的遗憾。
但是,不完美并不等于不伟大,伟大是一种本质,Java 动态代理就是佐例。