java反射之Method的invoke方法實現教程詳解
前言
在框架中經常會會用到method.invoke()方法,用來執行某個的物件的目標方法。以前寫程式碼用到反射時,總是獲取先獲取Method,然後傳入對應的Class例項物件執行方法。然而前段時間研究invoke方法時,發現invoke方法居然包含多型的特性,這是以前沒有考慮過的一個問題。那麼Method.invoke()方法的執行過程是怎麼實現的?它的多型又是如何實現的呢?
本文將從java和JVM的原始碼實現深入探討invoke方法的實現過程。
首先給出invoke方法多型特性的演示程式碼:
public class MethodInvoke { public static void main(String[] args) throws Exception { Method animalMethod = Animal.class.getDeclaredMethod("print"); Method catMethod = Cat.class.getDeclaredMethod("print"); Animal animal = new Animal(); Cat cat = new Cat(); animalMethod.invoke(cat); animalMethod.invoke(animal); catMethod.invoke(cat); catMethod.invoke(animal); } } class Animal { public void print() { System.out.println("Animal.print()"); } } class Cat extends Animal { @Override public void print() { System.out.println("Cat.print()"); } }
程式碼中,Cat類覆蓋了父類Animal的print()方法, 然後通過反射分別獲取print()的Method物件。最後分別用Cat和Animal的例項物件去執行print()方法。其中animalMethod.invoke(animal)和catMethod.invoke(cat),示例物件的真實型別和Method的宣告Classs是相同的,按照預期列印結果;animalMethod.invoke(cat)中,由於Cat是Animal的子類,按照多型的特性,子類呼叫父類的的方法,方法執行時會動態連結到子類的實現方法上。因此,這裡會呼叫Cat.print()方法;而catMethod.invoke(animal)中,傳入的引數型別Animal是父類,卻期望呼叫子類Cat的方法,因此這一次會丟擲異常。程式碼列印結果為:
Cat.print()
Animal.print()
Cat.print()
Exception in thread "main" java.lang.IllegalArgumentException: object is not an instance of declaring class
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)at java.lang.reflect.Method.invoke(Unknown Source)
at com.wy.invoke.MethodInvoke.main(MethodInvoke.java:17)
接下來,我們來看看invoke()方法的實現過程。
public Object invoke(Object obj,Object... args) throws IllegalAccessException,IllegalArgumentException,InvocationTargetException { if (!override) { if (!Reflection.quickCheckMemberAccess(clazz,modifiers)) { Class<?> caller = Reflection.getCallerClass(1); checkAccess(caller,clazz,obj,modifiers); } } MethodAccessor ma = methodAccessor; // read volatile if (ma == null) { ma = acquireMethodAccessor(); } return ma.invoke(obj,args); }
invoke()方法中主要分為兩部分:訪問控制檢查和呼叫MethodAccessor.invoke()實現方法執行。
首先看一下訪問控制檢查這一塊的邏輯。第一眼看到這裡的邏輯的時候,很容易搞不清楚是幹嘛的。通俗來講就是通過方法的修飾符(public/protected/private/package),來判斷方法的呼叫者是否可以訪問該方法。這是java的基礎內容,不過用程式碼寫出來,一下子不容易想到。訪問控制檢查分為3步:
- 檢查override,如果override為true,跳過檢查;否則繼續;
- 快速檢查,判斷該方法的修飾符modifiers是否為public,如果是跳過檢查;否則繼續;
- 詳細檢查,通過方法的(protected/private/package)修飾符或方法的宣告類(例如子類可以訪問父類的protected方法)與呼叫者caller之間的關係,判斷caller是否有許可權訪問該方法。
override屬性是Method的父類AccessibleObject中宣告的變數,使得程式可以控制是否跳過訪問許可權的檢查。另外,Method的例項物件中,override屬性的初始值設定為false。
public void setAccessible(boolean flag) throws SecurityException { SecurityManager sm = System.getSecurityManager(); if (sm != null) sm.checkPermission(ACCESS_PERMISSION); setAccessible0(this,flag); } private static void setAccessible0(AccessibleObject obj,boolean flag) throws SecurityException { if (obj instanceof Constructor && flag == true) { Constructor<?> c = (Constructor<?>)obj; if (c.getDeclaringClass() == Class.class) { throw new SecurityException("Can not make a java.lang.Class" + " constructor accessible"); } } obj.override = flag; }
多說一句,Field同樣繼承了AccessibleObject,且Field的override也是初始化為false的,也就是說並沒有按照變數的修飾符去初始化不同的值。但是我們在呼叫Field.set(Object obj,Object value)時,如果該Field是private修飾的,會因沒有訪問許可權而丟擲異常,因此必須呼叫setAccessible(true)。此處非常容易理解為因為變數是public的,所以override就被初始化為true。
invoke()方法中,訪問控制檢查之後,就是通過MethodAccessor.invoke()呼叫方法。再來看一下程式碼:
MethodAccessor ma = methodAccessor; // read volatile if (ma == null) { ma = acquireMethodAccessor(); } return ma.invoke(obj,args);
這裡的邏輯很簡單,首先將變數methodAccessor賦值給ma,在方法棧中儲存一個可以直接引用的本地變數,如果methodAccessor不存在,呼叫acquireMethodAccessor()方法建立一個。
private volatile MethodAccessor methodAccessor; private Method root; private MethodAccessor acquireMethodAccessor() { // First check to see if one has been created yet,and take it // if so MethodAccessor tmp = null; if (root != null) tmp = root.getMethodAccessor(); if (tmp != null) { methodAccessor = tmp; } else { // Otherwise fabricate one and propagate it up to the root tmp = reflectionFactory.newMethodAccessor(this); setMethodAccessor(tmp); } return tmp; } void setMethodAccessor(MethodAccessor accessor) { methodAccessor = accessor; // Propagate up if (root != null) { root.setMethodAccessor(accessor); } } Method copy() { Method res = new Method(clazz,name,parameterTypes,returnType,exceptionTypes,modifiers,slot,signature,annotations,parameterAnnotations,annotationDefault); res.root = this; res.methodAccessor = methodAccessor; return res; }
綜合acquireMethodAccessor(),setMethodAccessor()以及copy()這三個方法,可以看到一個Method例項物件維護了一個root引用。當呼叫Method.copy()進行方法拷貝時,root指向了被拷貝的物件。那麼當一個Method被多次拷貝後,呼叫一次setMethodAccessor()方法,就會將root引用所指向的Method的methodAccessor變數同樣賦值。例如:D -> C -> B -> A,其中X-> Y表示X = Y.copy(), 當C物件呼叫setMethodAccessor()時,B和A都會傳播賦值methodAccessor, 而D的methodAccessor還是null。緊接著,當D需要獲取methodAccessor而呼叫acquireMethodAccessor()時,D獲取root的methodAccessor, 那麼D將和ABC持有相同的methodAccessor。
雖然Method中,通過維護root引用意圖使相同的方法始終保持只有一個methodAccessor例項,但是上述方法仍然無法保證相同的方法只有一個methodAccessor例項。例如通過copy()使ABCD保持關係:D -> C -> B -> A, 當B物件呼叫setMethodAccessor()時,B和A都會賦值methodAccessor, 而C、D的methodAccessor還是null。這時D呼叫acquireMethodAccessor()時,D獲取root也就是C的methodAccessor,發現為空,然後就新建立了一個。從而出現了相同的方法中出現了兩個methodAccessor例項物件的現象。
在Class.getMethod()、Class.getDeclaredMethod()以及Class.getDeclaredMethod(String name,Class<?>... parameterTypes)方法中最終都會呼叫copy()方法來保障Method使用的安全性。 在比較極端加巧合的情況下,可能會引起類膨脹的問題,這就是接下來要講到的MethodAccessor的實現機制。
在前面程式碼中,MethodAccessor的建立是通過反射工廠ReflectionFactory的newMethodAccessor(Method)方法來建立的。
public MethodAccessor newMethodAccessor(Method method) { checkInitted(); if (noInflation) { return new MethodAccessorGenerator(). generateMethod(method.getDeclaringClass(),method.getName(),method.getParameterTypes(),method.getReturnType(),method.getExceptionTypes(),method.getModifiers()); } else { NativeMethodAccessorImpl acc = new NativeMethodAccessorImpl(method); DelegatingMethodAccessorImpl res = new DelegatingMethodAccessorImpl(acc); acc.setParent(res); return res; } }
其中, checkInitted()方法檢查從配置項中讀取配置並設定noInflation、inflationThreshold的值:
private static void checkInitted() { if (initted) return; AccessController.doPrivileged( new PrivilegedAction<Void>() { public Void run() { if (System.out == null) { // java.lang.System not yet fully initialized return null; } String val = System.getProperty("sun.reflect.noInflation"); if (val != null && val.equals("true")) { noInflation = true; } val = System.getProperty("sun.reflect.inflationThreshold"); if (val != null) { try { inflationThreshold = Integer.parseInt(val); } catch (NumberFormatException e) { throw (RuntimeException) new RuntimeException("Unable to parse property sun.reflect.inflationThreshold"). initCause(e); } } initted = true; return null; } }); }
可以通過啟動引數-Dsun.reflect.noInflation=false -Dsun.reflect.inflationThreshold=15來設定:
結合字面意思及下面程式碼理解,這兩個配置sun.reflect.noInflation是控制是否立即進行類膨脹,sun.reflect.inflationThreshold是設定類膨脹閾值。
建立MethodAccessor有兩種選擇,一種是當sun.reflect.noInflation配置項為true時,ReflectionFactory利用MethodAccessor的位元組碼生成類 MethodAccessorGenerator直接建立一個代理類,通過間接呼叫原方法完成invoke()任務,具體實現稍後給出。MethodAccessor的另一種實現方式是,建立DelegatingMethodAccessorImpl 委託類,並將執行invoke()方法的具體內容交由NativeMethodAccessorImpl實現,而NativeMethodAccessorImpl最終呼叫native方法完成invoke()任務。以下是NativeMethodAccessorImpl的invoke()方法實現。
public Object invoke(Object obj,Object[] args) throws IllegalArgumentException,InvocationTargetException { if (++numInvocations > ReflectionFactory.inflationThreshold()) { MethodAccessorImpl acc = (MethodAccessorImpl) new MethodAccessorGenerator(). generateMethod(method.getDeclaringClass(),method.getModifiers()); parent.setDelegate(acc); } return invoke0(method,args); } private static native Object invoke0(Method m,Object obj,Object[] args);
可以看到,當numInvocations數量大於配置項sun.reflect.inflationThreshold即類膨脹閾值時, 使用MethodAccessorGenerator建立一個代理類物件,並且將被委託的NativeMethodAccessorImpl的parent,也就是委託類DelegatingMethodAccessorImpl的委託類設定為這個生成的代理物件。這麼說可能有點繞,下面用一幅圖表示這個過程。
總體來說,當呼叫invoke()時,按照預設配置,Method首先建立一個DelegatingMethodAccessorImpl物件,並設定一個被委託的NativeMethodAccessorImpl物件,那麼method.invoke()就被轉換成DelegatingMethodAccessorImpl.invoke(),然後又被委託給NativeMethodAccessorImp.invoke()實現。當NativeMethodAccessorImp.invoke()呼叫次數超過一定熱度時(預設15次),被委託方又被轉換成代理類來實現。
之前提到過在極端情況下,同一個方法的Method物件存在多個不同拷貝拷貝時,可能存在多個MethodAccessor物件。那麼當多次呼叫後,必然會生成兩個重複功能的代理類。當然,一般情況下,生成兩個代理類並沒有較大的影響。
其中代理類的具體位元組碼實現過程較為複雜,大體思想是生成一個如下所示的類:
public class GeneratedMethodAccessor1 extends MethodAccessorImpl { public GeneratedMethodAccessor1 () { super(); } public Object invoke(Object obj,Object[] args) throws IllegalArgumentException,InvocationTargetException { if (!(obj instanceof Cat)) { throw new ClassCastException(); } if (args != null && args.length != 0) { throw new IllegalArgumentException(); } try { Cat cat = (Cat) obj; cat.print(); return null; } catch (Throwable e) { throw new InvocationTargetException(e,"invoke error"); } } }
到目前為止,除了在代理的GeneratedMethodAccessor1 類中,方法的執行有多型的特性,而NativeMethodAccessorImp的invoke()實現是在jdk中的完成的。接下來我們將目光移到NativeMethodAccessorImp的native方法invoke0();
openJDK下載地址
首先,在\jdk\src\share\native\sun\reflect路徑下找到NativeAccessors.c, 其中有Java_sun_reflect_NativeMethodAccessorImpl _invoke0()方法,根據JNI定義函式名的規則"包名_類名_方法名",這就是我們要找的native方法實現入口。
JNIEXPORT jobject JNICALL Java_sun_reflect_NativeMethodAccessorImpl_invoke0 (JNIEnv *env,jclass unused,jobject m,jobject obj,jobjectArray args) { return JVM_InvokeMethod(env,m,args); }
方法呼叫JVM_InvokeMethod(),一般以JVM_開頭的函式定義在jvm.cpp檔案中,不熟悉的話可以通過標頭檔案jvm.h看出來。繼續追蹤,發現jvm.cpp檔案位於spot\src\share\vm\prims資料夾下。
JVM_ENTRY(jobject,JVM_InvokeMethod(JNIEnv *env,jobject method,jobjectArray args0)) JVMWrapper("JVM_InvokeMethod"); Handle method_handle; if (thread->stack_available((address) &method_handle) >= JVMInvokeMethodSlack) { method_handle = Handle(THREAD,JNIHandles::resolve(method)); Handle receiver(THREAD,JNIHandles::resolve(obj)); objArrayHandle args(THREAD,objArrayOop(JNIHandles::resolve(args0))); oop result = Reflection::invoke_method(method_handle(),receiver,args,CHECK_NULL); jobject res = JNIHandles::make_local(env,result); if (JvmtiExport::should_post_vm_object_alloc()) { oop ret_type = java_lang_reflect_Method::return_type(method_handle()); assert(ret_type != NULL,"sanity check: ret_type oop must not be NULL!"); if (java_lang_Class::is_primitive(ret_type)) { // Only for primitive type vm allocates memory for java object. // See box() method. JvmtiExport::post_vm_object_alloc(JavaThread::current(),result); } } return res; } else { THROW_0(vmSymbols::java_lang_StackOverflowError()); } JVM_END
其中oop result = Reflection::invoke_method(method_handle(),CHECK_NULL)是方法的執行過程,在\hotspot\src\share\vm\runtime路徑下找到reflection.cpp檔案。
oop Reflection::invoke_method(oop method_mirror,Handle receiver,objArrayHandle args,TRAPS) { oop mirror = java_lang_reflect_Method::clazz(method_mirror); int slot = java_lang_reflect_Method::slot(method_mirror); bool override = java_lang_reflect_Method::override(method_mirror) != 0; objArrayHandle ptypes(THREAD,objArrayOop(java_lang_reflect_Method::parameter_types(method_mirror))); oop return_type_mirror = java_lang_reflect_Method::return_type(method_mirror); BasicType rtype; if (java_lang_Class::is_primitive(return_type_mirror)) { rtype = basic_type_mirror_to_basic_type(return_type_mirror,CHECK_NULL); } else { rtype = T_OBJECT; } instanceKlassHandle klass(THREAD,java_lang_Class::as_Klass(mirror)); Method* m = klass->method_with_idnum(slot); if (m == NULL) { THROW_MSG_0(vmSymbols::java_lang_InternalError(),"invoke"); } methodHandle method(THREAD,m); return invoke(klass,method,override,ptypes,rtype,true,THREAD); } oop Reflection::invoke(instanceKlassHandle klass,methodHandle reflected_method,bool override,objArrayHandle ptypes,BasicType rtype,bool is_method_invoke,TRAPS) { ResourceMark rm(THREAD); methodHandle method; // actual method to invoke KlassHandle target_klass; // target klass,receiver's klass for non-static // Ensure klass is initialized klass->initialize(CHECK_NULL); bool is_static = reflected_method->is_static(); if (is_static) { // ignore receiver argument method = reflected_method; target_klass = klass; } else { // check for null receiver if (receiver.is_null()) { THROW_0(vmSymbols::java_lang_NullPointerException()); } // Check class of receiver against class declaring method if (!receiver->is_a(klass())) { THROW_MSG_0(vmSymbols::java_lang_IllegalArgumentException(),"object is not an instance of declaring class"); } // target klass is receiver's klass target_klass = KlassHandle(THREAD,receiver->klass()); // no need to resolve if method is private or <init> if (reflected_method->is_private() || reflected_method->name() == vmSymbols::object_initializer_name()) { method = reflected_method; } else { // resolve based on the receiver if (reflected_method->method_holder()->is_interface()) { // resolve interface call if (ReflectionWrapResolutionErrors) { // new default: 6531596 // Match resolution errors with those thrown due to reflection inlining // Linktime resolution & IllegalAccessCheck already done by Class.getMethod() method = resolve_interface_call(klass,reflected_method,target_klass,THREAD); if (HAS_PENDING_EXCEPTION) { // Method resolution threw an exception; wrap it in an InvocationTargetException oop resolution_exception = PENDING_EXCEPTION; CLEAR_PENDING_EXCEPTION; JavaCallArguments args(Handle(THREAD,resolution_exception)); THROW_ARG_0(vmSymbols::java_lang_reflect_InvocationTargetException(),vmSymbols::throwable_void_signature(),&args); } } else { method = resolve_interface_call(klass,CHECK_(NULL)); } } else { // if the method can be overridden,we resolve using the vtable index. assert(!reflected_method->has_itable_index(),""); int index = reflected_method->vtable_index(); method = reflected_method; if (index != Method::nonvirtual_vtable_index) { // target_klass might be an arrayKlassOop but all vtables start at // the same place. The cast is to avoid virtual call and assertion. InstanceKlass* inst = (InstanceKlass*)target_klass(); method = methodHandle(THREAD,inst->method_at_vtable(index)); } if (!method.is_null()) { // Check for abstract methods as well if (method->is_abstract()) { // new default: 6531596 if (ReflectionWrapResolutionErrors) { ResourceMark rm(THREAD); Handle h_origexception = Exceptions::new_exception(THREAD,vmSymbols::java_lang_AbstractMethodError(),Method::name_and_sig_as_C_string(target_klass(),method->name(),method->signature())); JavaCallArguments args(h_origexception); THROW_ARG_0(vmSymbols::java_lang_reflect_InvocationTargetException(),&args); } else { ResourceMark rm(THREAD); THROW_MSG_0(vmSymbols::java_lang_AbstractMethodError(),method->signature())); } } } } } } // I believe this is a ShouldNotGetHere case which requires // an internal vtable bug. If you ever get this please let Karen know. if (method.is_null()) { ResourceMark rm(THREAD); THROW_MSG_0(vmSymbols::java_lang_NoSuchMethodError(),Method::name_and_sig_as_C_string(klass(),reflected_method->name(),reflected_method->signature())); } // In the JDK 1.4 reflection implementation,the security check is // done at the Java level if (!(JDK_Version::is_gte_jdk14x_version() && UseNewReflection)) { // Access checking (unless overridden by Method) if (!override) { if (!(klass->is_public() && reflected_method->is_public())) { bool access = Reflection::reflect_check_access(klass(),reflected_method->access_flags(),target_klass(),is_method_invoke,CHECK_NULL); if (!access) { return NULL; // exception } } } } // !(Universe::is_gte_jdk14x_version() && UseNewReflection) assert(ptypes->is_objArray(),"just checking"); int args_len = args.is_null() ? 0 : args->length(); // Check number of arguments if (ptypes->length() != args_len) { THROW_MSG_0(vmSymbols::java_lang_IllegalArgumentException(),"wrong number of arguments"); } // Create object to contain parameters for the JavaCall JavaCallArguments java_args(method->size_of_parameters()); if (!is_static) { java_args.push_oop(receiver); } for (int i = 0; i < args_len; i++) { oop type_mirror = ptypes->obj_at(i); oop arg = args->obj_at(i); if (java_lang_Class::is_primitive(type_mirror)) { jvalue value; BasicType ptype = basic_type_mirror_to_basic_type(type_mirror,CHECK_NULL); BasicType atype = unbox_for_primitive(arg,&value,CHECK_NULL); if (ptype != atype) { widen(&value,atype,ptype,CHECK_NULL); } switch (ptype) { case T_BOOLEAN: java_args.push_int(value.z); break; case T_CHAR: java_args.push_int(value.c); break; case T_BYTE: java_args.push_int(value.b); break; case T_SHORT: java_args.push_int(value.s); break; case T_INT: java_args.push_int(value.i); break; case T_LONG: java_args.push_long(value.j); break; case T_FLOAT: java_args.push_float(value.f); break; case T_DOUBLE: java_args.push_double(value.d); break; default: THROW_MSG_0(vmSymbols::java_lang_IllegalArgumentException(),"argument type mismatch"); } } else { if (arg != NULL) { Klass* k = java_lang_Class::as_Klass(type_mirror); if (!arg->is_a(k)) { THROW_MSG_0(vmSymbols::java_lang_IllegalArgumentException(),"argument type mismatch"); } } Handle arg_handle(THREAD,arg); // Create handle for argument java_args.push_oop(arg_handle); // Push handle } } assert(java_args.size_of_parameters() == method->size_of_parameters(),"just checking"); // All oops (including receiver) is passed in as Handles. An potential oop is returned as an // oop (i.e.,NOT as an handle) JavaValue result(rtype); JavaCalls::call(&result,&java_args,THREAD); if (HAS_PENDING_EXCEPTION) { // Method threw an exception; wrap it in an InvocationTargetException oop target_exception = PENDING_EXCEPTION; CLEAR_PENDING_EXCEPTION; JavaCallArguments args(Handle(THREAD,target_exception)); THROW_ARG_0(vmSymbols::java_lang_reflect_InvocationTargetException(),&args); } else { if (rtype == T_BOOLEAN || rtype == T_BYTE || rtype == T_CHAR || rtype == T_SHORT) narrow((jvalue*) result.get_value_addr(),CHECK_NULL); return box((jvalue*) result.get_value_addr(),CHECK_NULL); } }
Reflection::invoke_method()中呼叫Reflection::invoke(),然後在Reflection::invoke()方法中,當反射呼叫的方法是介面方法時,呼叫Reflection::resolve_interface_call(),該方法依賴LinkResolver::resolve_interface_call()來完成方法的動態連結過程,具體實現就不在這裡展示。
method = resolve_interface_call(klass,CHECK_(NULL));
methodHandle Reflection::resolve_interface_call(instanceKlassHandle klass,methodHandle method,KlassHandle recv_klass,TRAPS) { assert(!method.is_null(),"method should not be null"); CallInfo info; Symbol* signature = method->signature(); Symbol* name = method->name(); LinkResolver::resolve_interface_call(info,recv_klass,klass,KlassHandle(),false,CHECK_(methodHandle())); return info.selected_method(); }
如果反射呼叫的方法是可以被覆蓋的方法,例如Animal.print(), Reflection::invoke()最終通過查詢虛方法表vtable來確定最終的method。
// if the method can be overridden,""); int index = reflected_method->vtable_index(); method = reflected_method; if (index != Method::nonvirtual_vtable_index) { // target_klass might be an arrayKlassOop but all vtables start at // the same place. The cast is to avoid virtual call and assertion. InstanceKlass* inst = (InstanceKlass*)target_klass(); method = methodHandle(THREAD,inst->method_at_vtable(index)); }
總結
1.method.invoke()方法支援多型特性,其native實現在方法真正執行之前通過動態連線或者虛方法表來實現。
2.框架中使用method.invoke()執行方法呼叫時,初始獲取method物件時,可以先呼叫一次setAccessable(true),使得後面每次呼叫invoke()時,節省一次方法修飾符的判斷,略微提升效能。業務允許的情況下,Field同樣可以如此操作。
3.委託模式可以解決一種方案的多種實現之間自由切換,而代理模式只能根據傳入的被代理物件來實現功能。
到此這篇關於java反射之Method的invoke方法實現的文章就介紹到這了,更多相關java反射Method的invoke方法內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!
參考文章:
JAVA深入研究——Method的Invoke方法。