獲取函數
使用僞代碼來簡化源碼的複雜邏輯,只關注主線邏輯。
Java中萬物都是對象,如果使用過反射的同學,應該有所感受。Class是最基本的對象,而其中的函數(method)、屬性(field)等,也可以看作是一個對象。不然,method不是個東西,我們怎麼調用呢?
首先看下反射的使用
// 創建對象
Object innerClazz = InnerClazz.class.newInstance();
// 獲取函數
Method doTaskMethod = InnerClazz.class.getDeclaredMethod("doTask");
// 調用函數
doTaskMethod.invoke(innerClazz);
獲取所有函數信息
Class對象內部有一個函數:getDeclaredMethod,傳入函數名和參數後,可以得到對應的Method對象。該函數的實現僞代碼如下。
// *** 僞代碼 ***
void getDeclaredMethod(String methodName , Class<?> params){
// 從class中獲取所有的函數信息
Method[] declaredMethods = privateGetDeclaredMethods();
// 根據methodName 和 參數,查找method
Method method = searchMethod(declaredMethods, methodName, params);
}
首先,要獲取Method對象,那就從該Class對象中尋找。privateGetDeclaredMethods()就是獲取該Class對象的所有函數信息(返回的是Method數組)。接着,根據函數名(methodName)和函數參數(params)找到對應的Method對象。
// *** privateGetDeclaredMethods() 僞代碼 ***
Method[] privateGetDeclaredMethods(){
// 可以看成Class的緩存信息,裏面有函數信息、屬性信息等
ReflectionData<T> rd = reflectionData();
// 從緩存中獲取 函數列表
Method[] res = rd.declaredMethods;
if (res == null){
// res爲空 向jvm請求獲取res
res = JVM.getMethods();
// 緩存到rd中
rd.declaredMethods = res;
}
return res;
}
爲了提高性能,所以需要對所有 函數 進行一個緩存,緩存的對象就是ReflectionData。當發現緩存沒有,就可以使用native函數向JVM請求獲取函數信息,同時保存到緩存中。
查找對應的函數
經過上面的步驟後,緩存中保存了Class的函數信息,同時也返回了所有函數信息,現在就可以根據函數名 和 函數參數 對比查找需要反射的Method對象了。
Method searchMethods(Method[] methods, String methodName, Class<?> params){
Method res = null;
// for循環挨個比較
for(int i = 0; i<methods.length; i++){
if(滿足條件){
res = methods[i]
}
}
return res == null ? res.copy();
}
搜索函數很簡單,就是函數名和參數的比較,如果沒找到,就會在外部收到null,拋出NoSuchMethodException異常。
如果找到了,就複製一個該函數對象。相當於從原始的函數信息中複製一份出來。
函數的調用(invoke)
在獲取到了Method對象後,我們進行第二步:函數的調用。
// Method的invoke函數 僞代碼
Object invoke(Object obj, Object... args){
MethodAccessor ma = methodAccessor;
if (ma == null){
// 創建一個MethodAccessor
ma = acquireMethodAccessor();
}
return ma.invoke(obj,args);
}
Method 的 invoke函數內部調用了MethodAccessor的invoke函數。
MethodAccessor是個接口,實現類有3個,其中1個是作爲代理類,另外2個纔是真正幹活的。
代理類:DelegatingMethodAccessorImpl
幹活類:NativeMethodAccessorImpl 和 MagicAccessorImpl
MethodAccessor調用invoke後,進入DelegatingMethodAccessorImpl的invoke函數,其中再調用實際的幹活類。
// NativeMethodAccessorImpl的invoke
Object invoke(){
if(調用次數 > 15){
爲DelegatingMethodAccessorImpl 設置 MagicAccessorImpl作爲實際幹 活類。
}
調用 native 的invoke
}
在NativeMethodAccessorImpl的invoke函數中,會記錄該函數的調用次數,如果大於15次,就設置代理類的幹活類爲MagicAccessorImpl。
從類名中可以看出:NativeMethodAccessorImpl是native的調用者,MagicAccessorImpl相對的是Java層面的invoke調用者。
native函數底層是讓JVM調用JVM_InvokeMethod()函數
Java層的調用可以從MethodAccessorGenerator源碼中看出,實際是在內存中生成函數的字節碼,讓JVM直接調用。
其中,這裏最繞的就是調用次數達到了15次後,使用Java生成字節碼來實現函數的調用。
爲什麼大於15次需要更換delegate?
參考其他文章的結論:
動態實現(Java)和本地實現(native)相比,執行速度要快上20倍,這是因爲動態實現直接執行字節碼,不用從java到c++ 再到java 的轉換,但是因爲生成字節碼的操作比較耗費時間,所以如果僅一次調用的話反而是本地時間快3到4倍。
結尾
簡單從Java源碼層面分析了反射的執行過程,其中還有許多細節需要研究。
包括兩個實現類爲何執行速度相差這麼大?
native層面,是如何實現invoke的?
Android中的反射涉及到hidden API,是如何工作的?
參考
java 反射原理(jvm是如何實現反射的) - 簡書
JAVA深入研究——Method的Invoke方法。 - 寂靜沙灘 - 博客園
假笨說-從一起GC血案談到反射原理