Dalvik中Java Proxy实现机制分析

来源:互联网 发布:应用数据可以删吗 编辑:程序博客网 时间:2024/05/22 17:22

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

  1. 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出去.
  2. 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.
  3. generateProxy对应到native的dalvik/vm/native/java_lang_reflect_Proxy.cpp中的Dalvik_java_lang_reflect_Proxy_generateProxy:

    • 进一步调用dvmGenerateProxyClass.
  4. 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返回.
  5. 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作为输出.
  6. 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的依据.

一些补充:

  1. 关于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*)来进一步的使用

0 0
原创粉丝点击