您的位置:首页 > 编程语言 > Java开发

Dalvik中Java Proxy实现机制分析

2016-03-30 17:01 447 查看

下面的分析基于的JVM具体实现是Dalvik(当然了, 严格意义上讲, Dalvik没有完全的遵循JVM规范):

Java层Proxy:

通常的使用流程是使用其static的newProxyInstance(ClassLoader loader(该类加载器用于加载proxy class), Class<?>[] interfaces(一个Class(其实限定为interface)的数组, 每个都代表将被返回的proxy class要实现的接口), InvocationHandler h(负责处理方法分发的invocation handler)), 最后的是一个被handler所代理的proxy对象.

首先输入的InvocationHandler不能为null.

直接调用getProxyClass(loader, interfaces).getConstructor(new Class<?>[] { InvocationHandler.class }).newInstance(new Object[] { h }), 先根据给出的loader和interface构造出一个新的Class类型,然后获取该Class的构造函数(接受InvocationHandler参数)并构造出一个对象, 这个新的类已经实现了proxy功能.

下面全部是处理上面操作可能异常的代码. 任何异常都会被包装为InternalError重新trhow出去.

Class<?> getProxyClass(ClassLoader loader, Class<?>… interfaces) 实现了Proxy的关键功能. 生成了具有Proxy能力的Class.

遍历传入的Class列表:

如果该Class**不是接口(!isInterface()),则抛出异常**.

如果作为参数的classLoader不是该Class的classLoader, 那么会尝试使用loader来对该Class(获取了Class的name)进行load,如果load得到的Class对象和当前遍历到的Class不等, 那么会抛出异常.

然后遍历比较此Class元素在数组后面的所有元素, 如果有相等的, 那么抛出异常报告该Class在数组中出现了不止一次.

如果该Class不是Public的, 那么会判断, 这些Class是否属于一个Package.

获取loaderCache的锁进行同步操作:

以loader作为key从loaderCache中获取一个Map<String, WeakReference<Class<?>>>.

如果得到的是null, 会put一个new的.

如果Class数组长度为1, 那么直接用这唯一的一个Class的name作为interfaceKey。

否则,则以” “作为拼接符拼接数组所有Class的name.

从之前获取的Map interfaceCache中以interfaceKey来获取一个Class的弱引用:

如果没有, 那么nextClassName = “$Proxy” + NextClassNameIndex++;

如果之前获取了有效的commonPackageName, 那么nextClassName会在前面加上commonPackageName + “.”

如果没有提供loader, 那么取ClassLoader.getSystemClassLoader()作为loader.

调用native的generateProxy(native函数)(nextClassName.replace(‘.’, ‘/’), interfaces, loader)来生成一个新的Class.

将生成的Class以interfaceKey作为key放入到interfaceCache中, 保存的是指向Class的弱引用.

同步操作proxyCache将此Class作为key(value为”“,目前没有用上)放入到proxyCache, proxyCache主要用于isProxyClass()判断给定的Class是否为ProxyClass.

如果在cache中, 那么newClass = ref.get()(这里虽然是弱引用, 不过因为Class还会被以强引用的形式存入到proxyCache, 因此get()永远不会为null)

返回Class.

generateProxy对应到native的dalvik/vm/native/java_lang_reflect_Proxy.cpp中的Dalvik_java_lang_reflect_Proxy_generateProxy:

进一步调用dvmGenerateProxyClass.

dvmGenerateProxyClass(StringObject* str(要生成的类的名称), ArrayObject* interfaces(要实现的接口列表), Object* loader(该生成的类所靠挂的ClassLoader))

先调用dvmCreateCstrFromString(str)基于传入的JavaString构造一个C++的字符串, 存储类名.

下面的注释说明了构造出的类的特性:

是一个public final 的具体实现类

父类是*java.lang.reflect.Proxy

实现了接口列表的所有接口

对于复数个接口中定义命名相同的接口函数, 前面的接口覆盖后面的.

有一个构造函数, 其参数为InvocationHandler

已经override了hashCode, equals, and toString

有一个继承自Proxy的域, 类型为InvocationHandler的引用.

指导思想就是构造一个类, 并且仿照loadClassFromDex()来对其进行填充, 然后调用dvmLinkClass()来完成所有的累活(比如生成虚函数和接口方法表)

下面是为Class对象分配空间,并且设置一些基础属性:

需要分配的空间(即该Class的size)是: sizeof(ClassObject)(类对象本身的size) + kProxySFieldCount(=1) * sizeof(StaticField)(static域另占一部分空间)

调用dvmMalloc(newClassSize(上一步得到的需要为此Class分配的内存的大小), ALLOC_NON_MOVING), dvmMalloc如名就是dvm中的malloc, 也是从heap上分配空间, 保证了8字节对齐, 并且新分配的内存自动置0, 并将得到的内存指针直接转为ClassObject*指针(Malloc的惯例用法).

然后调用DVM_OBJECT_INIT(newClass, gDvm.classJavaLangClass)来初始化上面为ProxyClass分配的内存(Properly initialize an Object),真实操作是dvmSetFieldObject(obj, OFFSETOF_MEMBER(Object, clazz), clazz_), 其实就是将ProxyClass和gDvm.classJavaLangClass(也是一个ClassObject*)关联起来.

gDvm.classJavaLangClass的初始化在dalvik/vm/oo/Class.cpp的createInitialClasses()中, 该ClassObject*的descriptor的值为”Ljava/lang/Class;”, 即Java的Class类

调用dvmSetClassSerialNumber(newClass);来为这个新的Class分配一个独一无二的serialNumber.

newClass->descriptorAlloc = dvmNameToDescriptor(nameStr); 基于给出的Java层完全限定类名给出一个转化过的类名(比如: Ljava/lang/Object)

newClass->descriptor = newClass->descriptorAlloc;

SET_CLASS_FLAG(newClass, ACC_PUBLIC | ACC_FINAL);将该Class设置为public+final.

dvmSetFieldObject((Object *)newClass, OFFSETOF_MEMBER(ClassObject, super), (Object *)gDvm.classJavaLangReflectProxy); 将新类的父类指针指向gDvm.classJavaLangReflectProxy(对应的就是Java层的Proxy类), 即制定了父类

newClass->primitiveType = PRIM_NOT;PRIM_NOT对应于Object/array, 在这里显然应该是这个值.

dvmSetFieldObject((Object *)newClass, OFFSETOF_MEMBER(ClassObject, classLoader),(Object *)loader); 将此类的加载器制定为给出的加载器, 即代表这个新类是由该类加载器加载的

newClass->directMethodCount = 1; 新类有一个directMethod. (就是自动生成的<init>函数)

newClass->directMethods = (Method*) dvmLinearAlloc(newClass->classLoader, 1 * sizeof(Method)); 为directMethod分配空间, 使用了dvmLinearAlloc来分配 Method*(directMethodNum)的空间,并且挂接在directMethods上.

下面是添加虚函数的定义:

调用gatherMethods(interfaces, &methods, &throws, &methodCount), 来基于传入的接口列表得到要实现的方法的列表, 保存在Method **methods中, 数量保存在methodCount

newClass->virtualMethodCount = methodCount;, 新Class的虚函数数目自然就是上面汇总的虚函数的数目.

newClass->virtualMethods = (Method*)dvmLinearAlloc(newClass->classLoader, virtualMethodsSize); 为这些虚函数分配空间,并挂接在Class上.

dvmLinearReadOnly(newClass->classLoader, newClass->virtualMethods); 将上面分配到的虚函数的内存空间标记未只读.

然后是将接口列表附到新Class上, 代表新Class实现了这些接口.

关键一步: 对每个虚函数方法指针调用createHandlerMethod(newClass, &newClass->virtualMethods[i], methodsi)来完成Proxy功能

具体操作函数根据接口数量N分配sizeof(ClassObject*)*N的存储空间并挂在newClass->interfaces(指针数组), 每个interface指针指向(interfaces->contents)[i].

同样也会标记为只读.

设定Class的静态域:

newClass->sfieldCount = kProxySFieldCount(=1); 该类自己的静态变量只有一个.

设置新Class的sfields[kThrowsField(0)], clazz=newClass, name=”throws”, signature=”[[Ljava/lang/Throwable;”, accessFlags = ACC_STATIC | ACC_PRIVATE

最后dvmSetStaticFieldObject(sfield, (Object*)throws);为其设置一个值, throws在前面的gatherMethods(…)中被赋值了

newClass->status = CLASS_LOADED;, 设置新Class状态为以载入.

调用dvmLinkClass(newClass).对新类进行链接(包含了一系列操作,验证,解析等)Link a loaded class, Normally done as part of one of the “find class” variations, this is only called explicitly for synthetic class generation (e.g. reflect.Proxy).

最后万事OK, 将新Class加入到HashTable中.dvmAddClassToHash(newClass), 并且理论上不应该发生hash碰撞,否这意味着调用者提供过重复的类名.

最后将newClass返回.

gatherMethods(ArrayObject* interfaces, Method*** pMethods,

ArrayObject** pThrows, int* pMethodCount):

首先要计算出要实现的所有方法数,这样才能分配准确的空间:

Object本身有3个方法.

累加各个接口的方法数(Class的virtualMethodCount).

累加各个接口父类的方法数(遍历inerface的iftable,获取其clazz,累加该clazzz的virtualMethodCount).

得到了总方法数以后, 会根据该数量malloc两份内存空间,分别交于methods和allMethods.

因为前三个方法固定是Object的方法,因此会将allMethods[0~2]赋予Object的函数的地址:

ClassObject* obj = gDvm.classJavaLangObject(代表就是Object Class);

allMethods[0] = obj->vtable[gDvm.voffJavaLangObject_**equals**];

allMethods[1] = obj->vtable[gDvm.voffJavaLangObject_**hashCode**];

allMethods[2] = obj->vtable[gDvm.voffJavaLangObject_**toString**];

然后遍历每个interface, allMethods[allCount++] = &clazz->virtualMethods[XXX];将该interface的接口方法指针添加到allMethods中

然后是遍历收集异常.

最后对进行去重copyWithoutDuplicates(..), 返回的数量存在actualCount中.

copyWithoutDuplicates函数对Duplicate的定义是: 有相同的命名和参数列表, 不考虑返回类型

对于返回值不同其他相同的”dup”函数, 则采用返回值类型是最大兼容性的那个函数. 如

考虑有 class base, class sub extends base,class subsub extends sub,如果有dup的函数分别返回 base/sub/subsub, 那么会将返回subsub的那个函数作为被采用的函数,因为返回subsub可以保证最大的兼容性,某种意义上讲,对三个方法都实现了.

对每个最终被输出的method,都会有一个entry保留一个throwable数组与其对应表示该函数可能会抛出的异常, 对于不会抛异常的方法, 该entry为NULL.

经过”去重”的method会保存在methods中并最终赋给*pMethods作为输出. “去重”以后的方法数也会被赋给*pMethodCount作为输出.

createHandlerMethod(ClassObject* clazz, Method* dstMeth(新类的虚函数指针), const Method(要实现的函数指针)* srcMeth): 其作用就是将按照实现类的函数信息来配置ProxyClass的函数信息.

按照给定的接口的方法的命名和签名在新Class(ProxyClass)中创建(实现)一个方法.

dstMeth->clazz = clazz; 将方法关联到ProxyClass.

dstMeth->insns = (u2*) srcMeth; insns在Method定义中的注释意思是: actual code, instructions, in memory-mapped .dex, 指向的是真正的执行字节码, 为什么这么做,最后会说明.

dstMeth->accessFlags = ACC_PUBLIC | ACC_NATIVE;

dstMeth->name = srcMeth->name; 命名需要和”实现”的接口函数保持一致.

dstMeth->prototype = srcMeth->prototype; prototype的意思是: Method prototype descriptor string (return and argument types), 基本上等价于函数的signature+返回类型

dstMeth->shorty = srcMeth->shorty; short-form method descriptor string

int argsSize = dvmComputeMethodArgsSize(dstMeth) + 1; dstMeth->registersSize = dstMeth->insSize = argsSize; ProxyClass的Method的registersSize/insSize都是函数的参数个数+1

dstMeth->nativeFunc = proxyInvoker;, 最关键的一点, nativeFunc可以真正指向一个函数, 也可以是一个JNI bridge. 这里该函数被设置为proxyInvoker, 因为前面已经指明了该方法是native的

8. proxyInvoker(const u4* args(方法的输入参数), JValue* pResult(返回结果), const Method* method, Thread* self)

该函数是Proxy的方法的默认实现体, 通常这个方法的形式是这样的: public Object invoke(Object proxy, Method method, Object[] args).

这意味着必须去创建一个Method对象, 并且将参数打包进一个Object[],然后调用方法, 最后如果有返回,也需要解包.

Object* thisObj = (Object*) args[0];,调用方法所属的对象本身会作为一个参数被传入, 这里就是一个ProxyClass对象

handler = dvmGetFieldObject(thisObj, gDvm.offJavaLangReflectProxy_h);, 从thisObj中将Proxy类中声明的handler对象取出(Java类中定义为: protected InvocationHandler h;).

dvmFindVirtualMethodHierByDescriptor(handler->clazz, “invoke”, “(Ljava/lang/Object;Ljava/lang/reflect/Method;[Ljava/lang/Object;)Ljava/lang/Object;”); 从handler所属的Class中找到name(“invoke”)和desc匹配输入参数的Method, 会返回一个Method*

methodObj = dvmCreateReflectMethodObject((Method*) method->insns);基于上面找到的Method*的insns构造一个Java层的java.lang.reflect.Method对象.

通过dvmGetBoxedReturnType(method)得到该Method经过包装的返回值类型.

argArray = boxMethodArgs(method, args+1); Return a new Object[] array with the contents of “args”, 之所以args+1是因为第一个参数对象本身在这里不需要, 而如果得到输入参数的类型则是根据method的shorty[1]及”Z/C/F…/[/L”这样的描述来决定参数的类型

dvmCallMethod(self, invoke, handler, &invokeResult, thisObj, methodObj, argArray); 调用方法所在的线程(self)/Proxy对象/方法/Proxy的handler对象/返回结果的载体/输入参数等都就绪以后,可以调用该方法来执行Java层的 h.invoke(proxy(被调用方法的对象), method(被调用的方法), args)

调用完以后会调用dvmCheckException(self)来检查上面的执行是否有异常.

由上可见, 通过ProxyClass的任何的任何函数(更严格的说,是所有接口的接口函数), 都会被转接给其内部handler的invoke(….), 这也就是其某种意义上实现了AOP的依据.

一些补充:

关于insns在这里的使用:

在createHandlerMethod(…)中, 将dstMeth(也就是新Class的)的insns 设置为了(u2*) srcMeth; 刚看会觉得比较奇怪,insns应该指向的是执行字节码的地址,这里直接强制指向了一个Method对象的地址, 那么意味着dstMeth将完全不能工作, 因为根本找不到执行代码.

这个原因在后面的proxyInvoker(…)中已经说说明了, 有这么一段注释:

We don’t want to use “method”, because that’s the concrete implementation in the proxy class. We want the abstract Method from the declaring interface. We have a pointer to it tucked away in the “insns” field.

我们不希望使用”method”(这个method就是上面的dstMeth),因为它是ProxyClass的一个完全实现(同时也是完全无用的实现).我们想调用的是那些来自于那些接口的抽象方法. 而这些方法呢,我们正好在insns已经被上面的操作转化成了指向这个方法的指针.

从上面的注释可以看出, dstMeth原来的insns因为完全无用,所以”废物利用”, 用它来承载srcMethod的地址. 因为要把srcMeth直接传递给proxyInvoker(...)很麻烦,所以就在dstMeth的insns中保存了其地址. 因此才会又把insns强制转为(Method*)来进一步的使用
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: