Dalvik中Java Proxy實現機制分析

下面的分析基於的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*)來進一步的使用

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章