java

位置:IT落伍者 >> java >> 浏览文章

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


发布日期:2021年03月28日
 
Java动态代理机制综合分析及扩展

Java动态代理机制的出现使得Java开发人员不用手工编写代理类只要简单地指定一组接口及委托类对象便能动态地获得代理类这是一套非常灵活有弹性的代理框架

代理设计模式

代理是一种常用的设计模式其目的就是为其他对象提供一个代理以控制对某个对象的访问代理类负责为委托类预处理消息过滤消息并转发消息以及进行消息被委托类执行后的后续处理

为了保持行为的一致性代理类和委托类通常会实现相同的接口所以在访问者看来两者没有丝毫的区别通过代理类这中间一层能有效控制对委托类对象的直接访问也可以很好地隐藏和保护委托类对象同时也为实施不同控制策略预留了空间从而在设计上获得了更大的灵活性Java动态代理机制以巧妙的方式近乎完美地实践了代理模式的设计理念

相关的类和接口

要了解Java动态代理的机制首先需要了解以下相关的类或接口javalangreflectProxy这是Java动态代理机制的主类它提供了一组静态方法来为一组接口动态地生成代理类及其对象

清单Proxy的静态方法

//方法:该方法用于获取指定代理对象所关联的调用处理器

staticInvocationHandlergetInvocationHandler(Objectproxy)

//方法该方法用于获取关联于指定类装载器和一组接口的动态代理类的类对象

staticClassgetProxyClass(ClassLoaderloaderClass[]interfaces)

//方法该方法用于判断指定类对象是否是一个动态代理类

staticbooleanisProxyClass(Classcl)

//方法该方法用于为指定类装载器一组接口及调用处理器生成动态代理类实例

staticObjectnewProxyInstance(ClassLoaderloaderClass[]interfaces

InvocationHandlerh)

javalangreflectInvocationHandler这是调用处理器接口它自定义了一个invoke方法用于集中处理在动态代理类对象上的方法调用通常在该方法中实现对委托类的代理访问

清单InvocationHandler的核心方法

//该方法负责集中处理动态代理类上的所有方法调用第一个参数既是代理类实例第二个参数是被调用的方法对象

//第三个方法是调用参数调用处理器根据这三个参数进行预处理或分派到委托类实例上发射执行

Objectinvoke(ObjectproxyMethodmethodObject[]args)

每次生成动态代理类对象时都需要指定一个实现了该接口的调用处理器对象(参见Proxy静态方法的第三个参数)javalangClassLoader这是类装载器类负责将类的字节码装载到Java虚拟机(JVM)中并为其定义类对象然后该类才能被使用Proxy静态方法生成动态代理类同样需要通过类装载器来进行装载才能使用它与普通类的唯一区别就是其字节码是由JVM在运行时动态生成的而非预存在于任何一个class文件中

每次生成动态代理类对象时都需要指定一个类装载器对象(参见Proxy静态方法的第一个参数)

代理机制及其特点

首先让我们来了解一下如何使用Java动态代理具体有如下四步骤

通过实现InvocationHandler接口创建自己的调用处理器

通过为Proxy类指定ClassLoader对象和一组interface来创建动态代理类

通过反射机制获得动态代理类的构造函数其唯一参数类型是调用处理器接口类型

通过构造函数创建动态代理类实例构造时调用处理器对象作为参数被传入

清单动态代理对象创建过程

//InvocationHandlerImpl实现了InvocationHandler接口并能实现方法调用从代理类到委托类的分派转发

//其内部通常包含指向委托类实例的引用用于真正执行分派转发过来的方法调用

InvocationHandlerhandler=newInvocationHandlerImpl();

//通过Proxy为包括Interface接口在内的一组接口动态创建代理类的类对象

Classclazz=ProxygetProxyClass(classLoadernewClass[]{Interfaceclass});

//通过反射从生成的类对象获得构造函数对象

Constructorconstructor=clazzgetConstructor(newClass[]{InvocationHandlerclass});

//通过构造函数对象创建动态代理类实例

InterfaceProxy=(Interface)constructornewInstance(newObject[]{handler});

实际使用过程更加简单因为Proxy的静态方法newProxyInstance已经为我们封装了步骤到步骤的过程所以简化后的过程如下

清单简化的动态代理对象创建过程

//InvocationHandlerImpl实现了InvocationHandler接口并能实现方法调用从代理类到委托类的分派转发

InvocationHandlerhandler=newInvocationHandlerImpl();

//通过Proxy直接创建动态代理类实例

Interfaceproxy=(Interface)ProxynewProxyInstance(classLoader

newClass[]{Interfaceclass}

handler);

接下来让我们来了解一下Java动态代理机制的一些特点首先是动态生成的代理类本身的一些特点

)包如果所代理的接口都是public的那么它将被定义在顶层包(即包路径为空)如果所代理的接口中有非public的接口(因为接口不能被定义为protect或private所以除public之外就是默认的package访问级别)那么它将被定义在该接口所在包(假设代理了comibmdeveloperworks包中的某非public接口A那么新生成的代理类所在的包就是comibmdeveloperworks)这样设计的目的是为了最大程度的保证动态代理类不会因为包管理的问题而无法被成功定义并访问

)类修饰符该代理类具有final和public修饰符意味着它可以被所有的类访问但是不能被再度继承

)类名格式是$ProxyN其中N是一个逐一递增的阿拉伯数字代表Proxy类第N次生成的动态代理类值得注意的一点是并不是每次调用Proxy的静态方法创建动态代理类都会使得N值增加原因是如果对同一组接口(包括接口排列的顺序相同)试图重复创建动态代理类它会很聪明地返回先前已经创建好的代理类的类对象而不会再尝试去创建一个全新的代理类这样可以节省不必要的代码重复生成提高了代理类的创建效率

)类继承关系该类的继承关系如图

由图可见Proxy类是它的父类这个规则适用于所有由Proxy创建的动态代理类而且该类还实现了其所代理的一组接口这就是为什么它能够被安全地类型转换到其所代理的某接口的根本原因

接下来让我们了解一下代理类实例的一些特点每个实例都会关联一个调用处理器对象可以通过Proxy提供的静态方法getInvocationHandler去获得代理类实例的调用处理器对象

在代理类实例上调用其代理的接口中所声明的方法时这些方法最终都会由调用处理器的invoke方法执行此外值得注意的是代理类的根类javalangObject中有三个方法也同样会被分派到调用处理器的invoke方法执行它们是hashCodeequals和toString可能的原因有一是因为这些方法为public且非final类型能够被代理类覆盖二是因为这些方法往往呈现出一个类的某种特征属性具有一定的区分度所以为了保证代理类与委托类对外的一致性这三个方法也应该被分派到委托类执行

当代理的一组接口有重复声明的方法且该方法被调用时代理类总是从排在最前面的接口中获取方法对象并分派给调用处理器而无论代理类实例是否正在以该接口(或继承于该接口的某子接口)的形式被外部引用因为在代理类内部无法区分其当前的被引用类型

接着来了解一下被代理的一组接口有哪些特点首先要注意不能有重复的接口以避免动态代理类代码生成时的编译错误其次这些接口对于类装载器必须可见否则类装载器将无法链接它们将会导致类定义失败再次需被代理的所有非public的接口必须在同一个包中否则代理类生成也会失败最后接口的数目不能超过这是JVM设定的限制

最后再来了解一下异常处理方面的特点从调用处理器接口声明的方法中可以看到理论上它能够抛出任何类型的异常因为所有的异常都继承于Throwable接口但事实是否如此呢?答案是否定的原因是我们必须遵守一个继承原则即子类覆盖父类或实现父接口的方法时抛出的异常必须在原方法支持的异常列表之内所以虽然调用处理器理论上讲能够但实际上往往受限制除非父接口中的方法支持抛Throwable异常

那么如果在invoke方法中的确产生了接口方法声明中不支持的异常那将如何呢?放心Java动态代理类已经为我们设计好了解决方法它将会抛出UndeclaredThrowableException异常这个异常是一个RuntimeException类型所以不会引起编译错误通过该异常的getCause方法还可以获得原来那个不受支持的异常对象以便于错误诊断

代码是最好的老师

机制和特点都介绍过了接下来让我们通过源代码来了解一下Proxy到底是如何实现的首先记住Proxy的几个重要的静态变量

清单Proxy的重要静态变量

//映射表用于维护类装载器对象到其对应的代理类缓存

privatestaticMaploaderToCache=newWeakHashMap();

//标记用于标记一个动态代理类正在被创建中

privatestaticObjectpendingGenerationMarker=newObject();

//同步表记录已经被创建的动态代理类类型主要被方法isProxyClass进行相关的判断

privatestaticMapproxyClasses=CollectionssynchronizedMap(newWeakHashMap());

//关联的调用处理器引用

protectedInvocationHandlerh;

然后来看一下Proxy的构造方法

清单Proxy构造方法

//由于Proxy内部从不直接调用构造函数所以private类型意味着禁止任何调用

privateProxy(){}

//由于Proxy内部从不直接调用构造函数所以protected意味着只有子类可以调用

protectedProxy(InvocationHandlerh){thish=h;}

接着可以快速浏览一下newProxyInstance方法因为其相当简单

清单Proxy静态方法newProxyInstance

publicstaticObjectnewProxyInstance(ClassLoaderloader

Class<?>[]interfaces

InvocationHandlerh)

throwsIllegalArgumentException{

//检查h不为空否则抛异常

if(h==null){

thrownewNullPointerException();

}

//获得与制定类装载器和一组接口相关的代理类类型对象

Classcl=getProxyClass(loaderinterfaces);

//通过反射获取构造函数对象并生成代理类实例

try{

Constructorcons=clgetConstructor(constructorParams);

return(Object)consnewInstance(newObject[]{h});

}catch(NoSuchMethodExceptione){thrownewInternalError(etoString());

}catch(IllegalAccessExceptione){thrownewInternalError(etoString());

}catch(InstantiationExceptione){thrownewInternalError(etoString());

}catch(InvocationTargetExceptione){thrownewInternalError(etoString());

}

}

由此可见动态代理真正的关键是在getProxyClass方法该方法负责为一组接口动态地生成代理类类型对象在该方法内部您将能看到Proxy内的各路英雄(静态变量)悉数登场有点迫不及待了么?那就让我们一起走进Proxy最最神秘的殿堂去欣赏一番吧该方法总共可以分为四个步骤

对这组接口进行一定程度的安全检查包括检查接口类对象是否对类装载器可见并且与类装载器所能识别的接口类对象是完全相同的还会检查确保是interface类型而不是class类型这个步骤通过一个循环来完成检查通过后将会得到一个包含所有接口名称的字符串数组记为String[]interfaceNames总体上这部分实现比较直观所以略去大部分代码仅保留留如何判断某类或接口是否对特定类装载器可见的相关代码

清单通过ClassforName方法判接口的可见性

try{

//指定接口名字类装载器对象同时制定initializeBoolean为false表示无须初始化类

//如果方法返回正常这表示可见否则会抛出ClassNotFoundException异常表示不可见

interfaceClass=ClassforName(interfaceNamefalseloader);

}catch(ClassNotFoundExceptione){

}

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

清单缓存表的使用

do{

//以接口名字列表作为关键字获得对应cache值

Objectvalue=cacheget(key);

if(valueinstanceofReference){

proxyClass=(Class)((Reference)value)get();

}

if(proxyClass!=null){

//如果已经创建直接返回

returnproxyClass;

}elseif(value==pendingGenerationMarker){

//代理类正在被创建保持等待

try{

cachewait();

}catch(InterruptedExceptione){

}

//等待被唤醒继续循环并通过二次检查以确保创建完成否则重新等待

ntinue;

}else{

//标记代理类正在被创建

cacheput(keypendingGenerationMarker);

//break跳出循环已进入创建过程

break;

}while(true);

动态创建代理类的类对象首先是确定代理类所在的包其原则如前所述如果都为public接口则包名为空字符串表示顶层包如果所有非public接口都在同一个包则包名与这些接口的包名相同如果有多个非public接口且不同包则抛异常终止代理类的生成确定了包后就开始生成代理类的类名同样如前所述按格式$ProxyN生成类名也确定了接下来就是见证奇迹的发生动态生成代理类

清单动态生成代理类

//动态地生成代理类的字节码数组

byte[]proxyClassFile=ProxyGeneratorgenerateProxyClass(proxyNameinterfaces);

try{

//动态地定义新生成的代理类

proxyClass=defineClass(loaderproxyNameproxyClassFile

proxyClassFilelength);

}catch(ClassFormatErrore){

thrownewIllegalArgumentException(etoString());

}

//把生成的代理类的类对象记录进proxyClasses表

proxyClassesput(proxyClassnull);

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

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

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

代理类实现推演

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

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

清单代理类中方法调用的分派转发推演实现

//假设需代理接口Simulator

publicinterfaceSimulator{

shortsimulate(intarglongargStringarg)throwsExceptionAExceptionB;

}

//假设代理类为SimulatorProxy其类声明将如下

finalpublicclassSimulatorProxyimplementsSimulator{

//调用处理器对象的引用

protectedInvocationHandlerhandler;

//以调用处理器为参数的构造函数

publicSimulatorProxy(InvocationHandlerhandler){

thishandler=handler;

}

//实现接口方法simulate

publicshortsimulate(intarglongargStringarg)

throwsExceptionAExceptionB{

//第一步是获取simulate方法的Method对象

javalangreflectMethodmethod=null;

try{

thod=SimulatorclassgetMethod(

simulate

newClass[]{intclasslongclassStringclass});

}catch(Exceptione){

//异常处理(略)

}

//第二步是调用handler的invoke方法分派转发方法调用

Objectr=null;

try{

r=handlerinvoke(this

thod

//对于原始类型参数需要进行装箱操作

newObject[]{newInteger(arg)newLong(arg)arg});

}catch(Throwablee){

//异常处理(略)

}

//第三步是返回结果(返回类型是原始类型则需要进行拆箱操作)

return((Short)r)shortValue();

}

}

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

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

接下来让我们把注意力重新回到先前被淡化的错误处理上来在异常处理由于我们有理由确保所有的信息如接口名方法名和参数类型都准确无误所以这部分异常发生的概率基本为零所以基本可以忽略而异常处理我们需要思考得更多一些

回想一下接口方法可能声明支持一个异常列表而调用处理器invoke方法又可能抛出与接口方法不支持的异常再回想一下先前提及的Java动态代理的关于异常处理的特点对于不支持的异常必须抛UndeclaredThrowableException运行时异常所以通过再次推演我们可以得出一个更加清晰的异常处理的情况

清单细化的异常处理

Objectr=null;

try{

r=handlerinvoke(this

thod

newObject[]{newInteger(arg)newLong(arg)arg});

}catch(ExceptionAe){

//接口方法支持ExceptionA可以抛出

throwe;

}catch(ExceptionBe){

//接口方法支持ExceptionB可以抛出

throwe;

}catch(Throwablee){

//其他不支持的异常一律抛UndeclaredThrowableException

thrownewUndeclaredThrowableException(e);

}

这样我们就完成了对动态代理类的推演实现推演实现遵循了一个相对固定的模式可以适用于任意定义的任何接口而且代码生成所需的信息都是可知的那么有理由相信即使是机器自动编写的代码也有可能延续这样的风格至少可以保证这是可行的

美中不足

诚然Proxy已经设计得非常优美但是还是有一点点小小的遗憾之处那就是它始终无法摆脱仅支持interface代理的桎梏因为它的设计注定了这个遗憾回想一下那些动态生成的代理类的继承关系图它们已经注定有一个共同的父类叫ProxyJava的继承机制注定了这些动态代理类们无法实现对class的动态代理原因是多继承在Java中本质上就行不通

有很多条理由人们可以否定对class代理的必要性但是同样有一些理由相信支持class动态代理会更美好接口和类的划分本就不是很明显只是到了Java中才变得如此的细化如果只从方法的声明及是否被定义来考量有一种两者的混合体它的名字叫抽象类实现对抽象类的动态代理相信也有其内在的价值此外还有一些历史遗留的类它们将因为没有实现任何接口而从此与动态代理永世无缘如此种种不得不说是一个小小的遗憾但是不完美并不等于不伟大伟大是一种本质Java动态代理就是佐例

               

上一篇:Java GUI用户界面编程的基础

下一篇:java应用程序put操作