技术开发 频道

Java动态代理机制综合分析及扩展

  从loaderToCache映射表中获取以类装载器对象为关键字所对应的缓存表,如果不存在就创建一个新的缓存表并更新到loaderToCache。缓存表是一个HashMap实例,正常情况下它将存放键值对(接口名字列表,动态生成的代理类的类对象引用)。当代理类正在被创建时它会临时保存(接口名字列表,pendingGenerationMarker)。标记pendingGenerationMarke的作用是通知后续的同类请求(接口数组相同且组内接口排列顺序也相同)代理类正在被创建,请保持等待直至创建完成。

清单9.缓存表的使用
  
do{
  
//以接口名字列表作为关键字获得对应cache值  
Objectvalue=cache.get(key);
  
if(valueinstanceofReference){
  proxyClass
=(Class)((Reference)value).get();  }  if(proxyClass!=null){
  
//如果已经创建,直接返回
  returnproxyClass;
  }elseif(value
==pendingGenerationMarker){
  
//代理类正在被创建,保持等待
  try{  cache.wait();  }catch(InterruptedExceptione){  }
  
//等待被唤醒,继续循环并通过二次检查以确保创建完成,否则重新等待
  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(ClassFormatErrore){  thrownewIllegalArgumentException(e.toString());  }
  
//把生成的代理类的类对象记录进proxyClasses表  
proxyClasses.put(proxyClass,null);

  由此可见,所有的代码生成的工作都由神秘的ProxyGenerator所完成了,当你尝试去探索这个类时,你所能获得的信息仅仅是它位于并未公开的sun.misc包,有若干常量、变量和方法以完成这个神奇的代码生成的过程,但是sun并没有提供源代码以供研读。至于动态类的定义,则由Proxy的native静态方法defineClass0执行。

  代码生成过程进入结尾部分,根据结果更新缓存表,如果成功则将代理类的类对象引用更新进缓存表,否则清楚缓存表中对应关键值,最后唤醒所有可能的正在等待的线程。

  走完了以上四个步骤后,至此,所有的代理类生成细节都已介绍完毕,剩下的静态方法如getInvocationHandler和isProxyClass就显得如此的直观,只需通过查询相关变量就可以完成,所以对其的代码分析就省略了。

  代理类实现推演

  分析了Proxy类的源代码,相信在读者的脑海中会对Java动态代理机制形成一个更加清晰的理解,但是,当探索之旅在sun.misc.ProxyGenerator类处嘎然而止,所有的神秘都汇聚于此时,相信不少读者也会对这个ProxyGenerator类产生有类似的疑惑:它到底做了什么呢?它是如何生成动态代理类的代码的呢?诚然,这里也无法给出确切的答案。还是让我们带着这些疑惑,一起开始探索之旅吧。

  事物往往不像其看起来的复杂,需要的是我们能够化繁为简,这样也许就能有更多拨云见日的机会。抛开所有想象中的未知而复杂的神秘因素,如果让我们用最简单的方法去实现一个代理类,唯一的要求是同样结合调用处理器实施方法的分派转发,您的第一反应将是什么呢?“听起来似乎并不是很复杂”。的确,掐指算算所涉及的工作无非包括几个反射调用,以及对原始类型数据的装箱或拆箱过程,其他的似乎都已经水到渠成。非常地好,让我们整理一下思绪,一起来完成一次完整的推演过程吧。

清单11.代理类中方法调用的分派转发推演实现
  
//假设需代理接口Simulator  publicinterfaceSimulator{
  shortsimulate(intarg1,longarg2,Stringarg3)throwsExceptionA,ExceptionB;  }
  
//假设代理类为SimulatorProxy,其类声明将如下
finalpublicclassSimulatorProxyimplementsSimulator{
  
//调用处理器对象的引用  
protectedInvocationHandlerhandler;
  
//以调用处理器为参数的构造函数  
publicSimulatorProxy(InvocationHandlerhandler){  this.handler=handler;  }
  
//实现接口方法simulate
  publicshortsimulate(intarg1,longarg2,Stringarg3)  throwsExceptionA,ExceptionB{
  
//第一步是获取simulate方法的Method对象  
java.lang.reflect.Methodmethod=null;
  
try{
  method
=Simulator.class.getMethod(  "simulate"
 newClass[]{int.class,long.class,String.class});  }
catch(Exceptione){
//异常处理1(略)  }
  
//第二步是调用handler的invoke方法分派转发方法调用
  Objectr=null;
  
try{  r=handler.invoke(this,  method,
  
//对于原始类型参数需要进行装箱操作
  newObject[]{newInteger(arg1),newLong(arg2),arg3});
  }
catch(Throwablee){
  
//异常处理2(略)  }
  
//第三步是返回结果(返回类型是原始类型则需要进行拆箱操作)
  return((Short)r).shortValue();
  }
  }

  模拟推演为了突出通用逻辑所以更多地关注正常流程,而淡化了错误处理,但在实际中错误处理同样非常重要。从以上的推演中我们可以得出一个非常通用的结构化流程:第一步从代理接口获取被调用的方法对象,第二步分派方法到调用处理器执行,第三步返回结果。

  在这之中,所有的信息都是可以已知的,比如接口名、方法名、参数类型、返回类型以及所需的装箱和拆箱操作,那么既然我们手工编写是如此,那又有什么理由不相信ProxyGenerator不会做类似的实现呢?至少这是一种比较可能的实现。

0
相关文章