深入理解Java中的反射機制和使用原理!詳細解析invoke方法的執行和使用

攻城獅Chova發表於2021-06-20

反射的概念

  • 反射: Refelection,反射是Java的特徵之一,允許執行中的Java程式獲取自身資訊,並可以操作類或者物件的內部屬性
    • 通過反射,可以在執行時獲得程式或者程式中的每一個型別的成員活成成員的資訊
    • 程式中的物件一般都是在編譯時就確定下來,Java反射機制可以動態地建立物件並且呼叫相關屬性,這些物件的型別在編譯時是未知的
    • 也就是說 ,可以通過反射機制直接建立物件,即使這個物件型別在編譯時是未知的
  • Java反射提供下列功能:
    • 在執行時判斷任意一個物件所屬的類
    • 在執行時構造任意一個類的物件
    • 在執行時判斷任意一個類所具有的成員變數和方法,可以通過反射呼叫private方法
    • 在執行時呼叫任意一個物件的方法

反射的原理

  • 反射的核心: JVM在執行時才動態載入類或者呼叫方法以及訪問屬性,不需要事先(比如編譯時)知道執行物件是什麼
  • 類的載入:
    • Java反射機制是圍繞Class類展開的
    • 首先要了解類的載入機制:
      • JVM使用ClassLoader將位元組碼檔案,即 class檔案載入到方法區記憶體中
Class clazz = ClassLoader.getSystemClassLoader().loadClass("com.mypackage.MyClass");

ClassLoader類根據類的完全限定名載入類並返回一個Class物件

  • ReflectionData:
    • 為了提高反射的效能,必須要提供快取
    • class類內部使用一個useCaches靜態變數來標記是否使用快取
    • 這個值可以通過外部的sun.reflect.noCaches配置是否禁用快取
    • class類內部提供了一個ReflectionData內部類用來存放反射資料的快取,並宣告瞭一個reflectionData
    • 由於稍後進行按需延遲載入並快取,所以這個域並沒有指向一個例項化的ReflectionData物件
// 標記是否使用快取,可以通過外部的sun.reflect.noCaches配置是否禁用快取
private static boolean useCaches = true;

static class ReflectionData<T> {
	volatile Field[] declaredFields;
	volatile Field[] publicFields;
	volatile Method[] declaredMethods;
	volatile Method[] publicMethods;
	volatile Constructor<T>[] declaredConstructors;
	volatile Constructors<T>[] publicConstructors;
	volatile Field[] declaredPublicFields;
	volatile Method[] declaredPublicMethods;
	final int redefinedCount;

	ReflectionData(int redefinedCount) {
		this.redefinedCount = redefinedCount;
	}
}
	
	// 這個是SoftReference,在記憶體資源緊張的時候可能會被回收
	// volatile保證多執行緒環境下讀寫的正確性
	 private volatile transient SoftReference<RefelectionData<T>> reflectionData;

	// 這個主要用於和ReflectionData中的redefinedCount進行比較
	// 如果兩個值不相等,說明ReflectionData快取的資料已經過期了
	private volatile transient classRedefinedCount = 0;

反射的主要用途

  • 反射最重要的用途就是開發各種通用框架
    • 很多框架都是配置化的,通過XML檔案配置Bean
    • 為了保證框架的通用性,需要根據配置檔案載入不同的物件或者類,呼叫不同的方法
    • 要運用反射,執行時動態載入需要載入的物件
  • 示例:
    • 在運用Struts 2框架的開發中會在struts.xml中配置Action:
	  <action name="login"
               class="org.ScZyhSoft.test.action.SimpleLoginAction"
               method="execute">
           <result>/shop/shop-index.jsp</result>
           <result name="error">login.jsp</result>
       </action>
  • 配置檔案與Action建立對映關係
  • View層發出請求時,請求會被StrutsPrepareAndExecuteFilter攔截
  • StrutsPrepareAndExecuteFilter會動態地建立Action例項
    • 請求login.action
    • StrutsPrepareAndExecuteFilter會解析struts.xml檔案
    • 檢索actionnameloginAction
    • 根據class屬性建立SimpleLoginAction例項
    • 使用invoke方法呼叫execute方法
  • 反射是各種容器實現的核心

反射的運用

  • 反射相關的類在StrutsPrepareAndExecuteFilter
  • 反射可以用於:
    • 判斷物件所屬的類
    • 獲得class物件
    • 構造任意一個物件
    • 呼叫一個物件
  • 九大預定義的Class物件:
    • 基本的Java型別: boolean, byte, char, short, int, long, float, double
    • 關鍵字void通過class屬性的也表示為Class物件
    • 包裝類或者void類的靜態TYPE欄位:
      • Integer.TYPE == int.class
      • Integer.class == int.class
    • 陣列型別的Class例項物件:
      • Class<String[]> clz = String[].class;
      • 陣列的Class物件如何比較是否相等:
        • 陣列的維數
        • 陣列的型別
        • Class類中的isArray(),用來判斷是否表示一個陣列型別

獲得Class物件

  • 使用Class類的forName靜態方法:
public static Class<?> forName(String className);



/* 在JDBC中使用這個方法載入資料庫驅動 */
Class.forName(driver);
  • 直接獲取一個物件的class:
Class<?> klass=int.class;
Class<?> classInt=Integer.TYPE;
  • 呼叫物件的getClass()方法:
StringBuilder str=new StringBuilder("A");
Class<?> klass=str.getClass();

判斷是否是某個類的例項

  • 一般來說,使用instanceof關鍵字判斷是否為某個類的例項
  • 在反射中,可以使用Class物件的isInstance() 方法來判斷是否為某個類的例項,這是一個native方法
public native boolean isInstance(Object obj);

建立例項

通過反射生成物件的例項主要有兩種方式:

  • 使用Class物件的newInstance()方法來建立Class物件對應類的例項:
Class<?> c = String.class;
Object str = c.newInstance();
  • 先通過Class物件獲取指定的Constructor物件,再呼叫Constructor物件的newInstance()方法來建立例項: 可以用指定的構造器構造類的例項
/* 獲取String所對應的Class物件 */
Class<?> c=String.class;

/* 獲取String類帶一個String引數的構造器 */
Constructor constructor=c.getConstructor(String.class);

/* 根據構造器建立例項 */
Object obj=constructor.newInstance("abc");
System.out.println(obj);

獲取方法

獲取Class物件的方法集合,主要有三種方法:

  • getDeclaredMethods(): 返回類或介面宣告的所有方法:
    • 包括公共,保護,預設(包)訪問和私有方法
    • 不包括繼承的方法
public Method[] getDeclaredMethods() throws SecurityException {}
  • getMethods(): 返回某個類所有的public方法
    • 包括繼承類的public方法
public Method[] getMethods() throws SecurityException {}
  • getMethod(): 返回一個特定的方法
    • 第一個引數 :方法名稱
    • 後面的引數 :方法的引數對應Class的物件
public Method getMethod(String name,Class<?>... parameterType) {}
  • 獲取方法示例:
public class MethodTest {
	public static void methodTest() throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
		Class<?> c = methodClass.class;
		Object object = c.newInstance();
		Method[] methods = c.getMethods();
		Method[] declaredMethods = c.getDeclaredMethods();
		// 獲取methodClass類中的add方法
		Method method = c.getMethod("add", int.class, int.class);
		// getMethods()方法獲取的所有方法
		System.out.println("getMethods獲取的方法:");
		for (Method m:methods)
			System.out.println(m);
		// getDeclaredMethods()方法獲取的所有方法
		System.out.println("getDeclaredMethods獲取的方法:");
		for (Method m:declaredMethods)
			System.out.println(m);
	}
}

class methodClass {
	public final int n = 3;
	
	public int add(int a, int b) {
		return a + b;
	}
	
	public int sub(int a, int b) {
		return a * b;
	}
}

程式執行結果:

getMethods獲取的方法:
public int org.ScZyhSoft.common.methodClass.add(int,int)
public int org.ScZyhSoft.common.methodClass.sub(int,int)
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public java.lang.String java.lang.Object.toString()
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
getDeclaredMethods獲取的方法:
public int org.ScZyhSoft.common.methodClass.add(int,int)
public int org.ScZyhSoft.common.methodClass.sub(int,int)

通過getMethods() 獲取的方法可以獲取到父類的方法

獲取構造器資訊

  • 通過Class類的getConstructor方法得到Constructor類的一個例項
  • Constructor類中newInstance方法可以建立一個物件的例項:
public T newInstance(Objec ... initargs)

newInstance方法可以根據傳入的引數來呼叫對應的Constructor建立物件的例項

獲取類的成員變數資訊

  • getFileds: 獲取公有的成員變數
  • getDeclaredFields: 獲取所有已宣告的成員變數,但是不能得到父類的成員變數

呼叫方法

  • 從類中獲取一個方法後,可以使用invoke() 來呼叫這個方法
public Object invoke(Object obj, Object ... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {}
  • 示例:
public class InvokeTest {
	public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
		Class<?> klass = method.class;
		// 建立methodClass的例項
		Object obj = klass.newInstance();
		// 獲取methodClass的add方法
		Method method = klass.getMethod("add", int.class, int.class);
		// 呼叫method對應的方法,實現add(1,4)
		Object result = method.invoke(obj, 1, 4);
		System.out.println(result);
	}
}

class methodClass {
	public final int n = 3;
	public int add(int a, int b) {
		return a + b;
	}
	public int sub(int a,int b) {
		return a * b;
	}
}

利用反射建立陣列

  • 陣列是Java中一種特殊的資料型別,可以賦值給一個Object Reference
  • 利用反射建立陣列的示例:
public static void ArrayTest() throws ClassNotFoundException {
	Class<?> cls = class.forName("java.lang.String");
	Object array = Array.newInstance(cls, 25);
	// 在陣列中新增資料
	Array.set(array, 0, "C");
	Array.set(array, 1, "Java");
	Array.set(array, 2, "Python");
	Array.set(array, 3, "Scala");
	Array.set(array, 4, "Docker");
	// 獲取資料中的某一項內容
	System.out.println(Array.get(array, 3));
}

Array類是java.lang.reflect.Array類,通過Array.newInstance() 建立陣列物件:

public static Object newInstance(Class<?> componentType, int length) throws NegativeArraySizeException {
	return newArray(componentType, length);
}

newArray方法是一個native方法,具體實現在HotSpot JVM中,原始碼如下:

private static native Object newArray(Class<?> componentType, int length) throws NegativeArraySizeException;


------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


- newArray原始碼目錄: openjdk\hotspot\src\share\vm\runtime\reflection.cpp

arrayOop Reflection::reflect_new_array(oop element_mirror, jint length, TRAPS) {
  if (element_mirror == NULL) {
    THROW_0(vmSymbols::java_lang_NullPointerException());
  }
  if (length < 0) {
    THROW_0(vmSymbols::java_lang_NegativeArraySizeException());
  }
  if (java_lang_Class::is_primitive(element_mirror)) {
    Klass* tak = basic_type_mirror_to_arrayklass(element_mirror, CHECK_NULL);
    return TypeArrayKlass::cast(tak)->allocate(length, THREAD);
  } else {
    Klass* k = java_lang_Class::as_Klass(element_mirror);
    if (k->oop_is_array() && ArrayKlass::cast(k)->dimension() >= MAX_DIM) {
      THROW_0(vmSymbols::java_lang_IllegalArgumentException());
    }
    return oopFactory::new_objArray(k, length, THREAD);
  }
}
  • Array類的setget方法都是native方法,具體實現在HotSpot JVM中,對應關係如下:
    • set: Reflection::array_set
    • get: Reflection::array_get

invoke方法

  • 在Java中很多方法都會呼叫invoke方法,很多異常的丟擲多會定位到invoke方法:
java.lang.NullPointerException
  at ......
  at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
  at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
  at java.lang.reflect.Method.invoke(Method.java:497)

invoke執行過程

  • invoke方法用來在執行時動態地呼叫某個例項的方法,實現如下:
@CallSensitive
public Object invoke(Object obj, Object ... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
	if (!override) {
		if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
			Class<?> caller = Refelection.getCallerClass();
			checkAccess(caller, clazz, obj, modifiers);
		}
	}
	MethodAccessor ma = methodAccessor;
	if (ma == null) {
		ma = acquireMethodAccessor();
	}
	return ma.invoke(obj, args);
}

許可權檢查

  • AccessibleObject類是Field,MethodConstructor物件的基類:
    • 提供將反射的物件標記為在使用時取消預設Java語言訪問控制檢查的能力
  • invoke方法會首先檢查AccessibleObjectoverride屬性的值:
    • override預設值為false:
      • 表示需要許可權呼叫規則,呼叫方法時需要檢查許可權
      • 也可以使用setAccessible() 設定為true
    • override如果值為true:
      • 表示忽略許可權規則,呼叫方法時無需檢查許可權
      • 也就是說,可以呼叫任意private方法,違反了封裝
  • 如果override屬性為預設值false,則進行進一步的許可權檢查:
  1. 首先用Reflection.quickCheckMemberAccess(clazz, modifiers) 方法檢查方法是否為public
    1.1 如果是public方法的話,就跳過本步
    1.2 如果不是public方法的話,就用Reflection.getCallerClass()方法獲取呼叫這個方法的Class物件,這是一個native方法
@CallerSensitive
	public static native Class<?> getCallerClass();

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------


- 在OpenJDK中可以找到getCallerClass方法的JNI入口-Reflection.c

JNIEXPORT jclass JNICALL Java_sun_reflect_Reflection_getCallerClass__
(JNIEnv *env, jclass unused)
{
    return JVM_GetCallerClass(env, JVM_CALLER_DEPTH);
}

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------



- JVM_GetCallerClass的原始碼位於jvm.cpp中

VM_ENTRY(jclass, JVM_GetCallerClass(JNIEnv* env, int depth))
  JVMWrapper("JVM_GetCallerClass");
  // Pre-JDK 8 and early builds of JDK 8 don't have a CallerSensitive annotation; or
  // sun.reflect.Reflection.getCallerClass with a depth parameter is provided
  // temporarily for existing code to use until a replacement API is defined.
  if (SystemDictionary::reflect_CallerSensitive_klass() == NULL || depth != JVM_CALLER_DEPTH) {
    Klass* k = thread->security_get_caller_class(depth);
    return (k == NULL) ? NULL : (jclass) JNIHandles::make_local(env, k->java_mirror());
  }
  // Getting the class of the caller frame.
  //
  // The call stack at this point looks something like this:
  //
  // [0] [ @CallerSensitive public sun.reflect.Reflection.getCallerClass ]
  // [1] [ @CallerSensitive API.method                                   ]
  // [.] [ (skipped intermediate frames)                                 ]
  // [n] [ caller                                                        ]
  vframeStream vfst(thread);
  // Cf. LibraryCallKit::inline_native_Reflection_getCallerClass
  for (int n = 0; !vfst.at_end(); vfst.security_next(), n++) {
    Method* m = vfst.method();
    assert(m != NULL, "sanity");
    switch (n) {
    case 0:
      // This must only be called from Reflection.getCallerClass
      if (m->intrinsic_id() != vmIntrinsics::_getCallerClass) {
        THROW_MSG_NULL(vmSymbols::java_lang_InternalError(), "JVM_GetCallerClass must only be called from Reflection.getCallerClass");
      }
      // fall-through
    case 1:
      // Frame 0 and 1 must be caller sensitive.
      if (!m->caller_sensitive()) {
        THROW_MSG_NULL(vmSymbols::java_lang_InternalError(), err_msg("CallerSensitive annotation expected at frame %d", n));
      }
      break;
    default:
      if (!m->is_ignored_by_security_stack_walk()) {
        // We have reached the desired frame; return the holder class.
        return (jclass) JNIHandles::make_local(env, m->method_holder()->java_mirror());
      }
      break;
    }
  }
  return NULL;
JVM_END
  1. 獲取Class物件caller後使用checkAccess方法進行一次快速的許可權校驗 ,checkAccess方法實現如下:
volatile Object securityCheckCache;

	void checkAccess(Class<?> caller, Class<?> clazz, Object obj, int modifiers) throws IllegalAccessException {
		if(caller == clazz){	// 快速校驗
			return;				// 許可權通過校驗
		}
		Object cache = securityCheckCache;	// 讀取volatile
		Class<?> targetClass = clazz;
		if (obj != null && Modifier.isProtected(modifiers) && ((targetClass = obj.getClass()) != clazz)) {	// 必須匹配caller,targetClass中的一個
			if (cache instanceof Class[]) {
				Class<?>[] cache2 = (Class<?>[]) cache;
				if (cache2[1] == targetClass && cache[0] == caller) {
					return;		// 校驗通過
				}
			}
		} else if (cache == caller) {
			return;				// 校驗通過
		}
		slowCheckMemberAccess(caller, clazz, obj, modifiers, targetClass);
	}

首先先執行一次快速校驗,一旦Class正確則許可權檢查通過;如果未通過,則建立一個快取,中間再進行檢查

  • 如果上面所有的許可權檢查都未通過,將會執行更詳細的檢查:
void slowCheckMemberAccess(Class<?> caller, Class<?> clazz, Object obj, int modifiers, Class<?> targetClass) throws IllegalAccessException {
	Refelection.ensureMemberAccess(caller, clazz, obj, modifiers);
	// 如果成功,就更新快取
	Object cache = ((targetClass == clazz) ? caller : new Class<?>[] {caller, targetClass});
	securityCheckCache = cache;
}

Reflection.ensureMemberAccess方法繼續檢查許可權.若檢查通過就更新快取,這樣下一次同一個類呼叫同一個方法時就不用執行許可權檢查了,這是一種簡單的快取機制
由於JMMhappens-before規則能夠保證快取初始化能夠在寫快取之間發生,因此兩個cache不需要宣告為volatile

  • 檢查許可權的工作到此結束.如果沒有通過檢查就會丟擲異常,如果通過檢查就會到下一步

呼叫MethodAccessor的invoke方法

  • Method.invoke() 不是自身實現反射呼叫邏輯,而是通過sun.refelect.MethodAccessor來處理
  • Method物件的基本構成:
    • 每個Java方法有且只有一個Method物件作為root, 相當於根物件,對使用者不可見
    • 當建立Method物件時,程式碼中獲得的Method物件相當於其副本或者引用
    • root物件持有一個MethodAccessor物件,所有獲取到的Method物件都共享這一個MethodAccessor物件
    • 必須保證MethodAccessor在記憶體中的可見性
  • root物件及其宣告:
private volatile MethodAccessor methodAccessor;
/**
 * For sharing of MethodAccessors. This branching structure is
 * currently only two levels deep (i.e., one root Method and
 * potentially many Method objects pointing to it.)
 * 
 * If this branching structure would ever contain cycles, deadlocks can
 * occur in annotation code.
 */
private Method  root;
  • MethodAccessor:
/**
 * This interface provides the declaration for
 * java.lang.reflect.Method.invoke(). Each Method object is
 * configured with a (possibly dynamically-generated) class which
 * implements this interface
 */
 public interface MethodAccessor {
 	// Matches specification in {@link java.lang.reflect.Method}
 	public Object invoke(Object obj, Object[] args) throws IllegalArgumentException, InvocationTargetException;
 }

MethodAccessor是一個介面,定義了invoke() 方法,通過Usage可以看出MethodAccessor的具體實現類:

  1. sun.reflect.DelegatingMethodAccessorImpl
  2. sun.reflect.MethodAccessorImpl
  3. sun.reflect.NativeMethodAccessorImpl
  • 第一次呼叫Java方法對應的Method物件的invoke()方法之前,實現呼叫邏輯的MethodAccess物件還沒有建立
  • 第一次呼叫時,才開始建立MethodAccessor並更新為root, 然後呼叫MethodAccessor.invoke() 完成反射呼叫
/**
 * NOTE that there is no synchronization used here. 
 * It is correct(though not efficient) to generate more than one MethodAccessor for a given Method.
 * However, avoiding synchronization will probably make the implementation more scalable.
 */

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 {
		tmp = reflectionFactory.newMethodAccessor(this);
		setMethodAccessor(tmp);
	} 
	return tmp;
} 
  • methodAccessor例項由reflectionFactory物件操控生成 ,reflectionFactory是在AccessibleObject中宣告的:
/**
 * Reflection factory used by subclasses for creating field,
 * method, and constructor accessors. Note that this is called very early in the bootstrapping process.
 */
static final ReflectionFactory reflectionFactory = AccessController.doPrivileged(
													new sun.reflect.ReflectionFactory.GetReflectionFactoryAction());
  • sun.reflect.ReflectionFactory方法:
public class ReflectionFactory {
	private static boolean initted = false;
	private static Permission reflectionFactoryAccessPerm = new RuntimePermission("reflectionFactoryAccess");
	private static ReflectionFactory soleInstance = new ReflectionFactory();
	// Provides access to package-private mechanisms in java.lang.reflect
	private static volatile LangReflectAccess langReflectAccess;

	/**
	 * "Inflation" mechanism. Loading bytecodes to implement Method.invoke() and Constructor.
	 * newInstance() currently costs 3-4x more than an invocation via native code for the first invocation (though subsequent invocations have been benchmarked to be over 20x faster)
	 * Unfortunately this cost increases startup time for certain applications that use reflection intensively (but only once per class) to bootstrap themselves
	 * To avoid this penalty we reuse the existing JVM entry points for the first few invocations of Methods and Constructors and then switch to the bytecode-based implementations
	 */

	// Package-private to be accessible to NativeMethodAccessorImpl and NativeConstructorAccessorImpl
	private static noInflation = false;
	private static int inflationThreshold = 15;

	// 生成MethodAccessor
	public MethodAccessor newMethodAccessor(Method method) {
		checkInitted();

		if (noInflation && !ReflectUtil.isVMAnonymousClass(method.getDeclaringClass())) {
			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;
		}
	}

	/**
	 * We have to defer full initialization of this class until after the static initializer is run since java.lang.reflect
     * Method's static initializer (more properly, that for java.lang.reflect.AccessibleObject) causes this class's to be run, before the system properties are set up
	 */
	 private static void checkInitted() {
	 	if (initted) return;
	 	AccessController.doPrivileged(
            new PrivilegedAction<Void>() {
                public Void run() {
                /**
                 * Tests to ensure the system properties table is fully initialized
                 * This is needed because reflection code is called very early in the initialization process (before command-line arguments have been parsed and therefore these user-settable properties installed
                 * We assume that if System.out is non-null then the System class has been fully initialized and that the bulk of the startup code has been 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 new RuntimeException("Unable to parse property sun.reflect.inflationThreshold", e);
                        }
                    }
                    initted = true;
                    return null;
                }
            });
    }
}
  • 實際的MethodAccessor實現有兩個版本,一個是Java版本,一個是native版本,兩者各有特點:
    • 初次啟動時Method.invoke()Constructor.newInstance() 方法採用native方法要比Java方法快3-4倍
    • 啟動後native方法又要消耗額外的效能而慢於Java方法
    • Java實現的版本在初始化時需要較多時間,但長久來說效能較好
  • 這是HotSpot的優化方式帶來的效能特性:
    • 跨越native邊界會對優化有阻礙作用
  • 為了儘可能地減少效能損耗,HotSpot JDK採用inflation方式:
    • Java方法在被反射呼叫時,開頭若干次使用native版
    • 等反射呼叫次數超過閾值時則生成一個專用的MethodAccessor實現類,生成其中的invoke() 方法的位元組碼
    • 以後對該Java方法的反射呼叫就會使用Java版本
  • ReflectionFactory.newMethodAccessor() 生成MethodAccessor物件的邏輯:
    • native版開始會會生成NativeMethodAccessorImplDelegatingMethodAccessorImpl兩個物件
  • DelegatingMethodAccessorImpl:
/* Delegates its invocation to another MethodAccessorImpl and can change its delegate at run time */
class DelegatingMethodAccessorImpl extends MethodAccessorImpl {
    private MethodAccessorImpl delegate;
    DelegatingMethodAccessorImpl(MethodAccessorImpl delegate) {
        setDelegate(delegate);
    }
    public Object invoke(Object obj, Object[] args)
        throws IllegalArgumentException, InvocationTargetException
    {
        return delegate.invoke(obj, args);
    }
    void setDelegate(MethodAccessorImpl delegate) {
        this.delegate = delegate;
    }
}

DelegatingMethodAccessorImpl物件是一箇中間層,方便在native版與Java版的MethodAccessor之間進行切換

  • native版MethodAccessor的Java方面的宣告: sun.reflect.NativeMethodAccessorImpl
/* Used only for the first few invocations of a Method; afterward,switches to bytecode-based implementation */
class NativeMethodAccessorImpl extends MethodAccessorImpl {
    private Method method;
    private DelegatingMethodAccessorImpl parent;
    private int numInvocations;
    NativeMethodAccessorImpl(Method method) {
        this.method = method;
    }
    public Object invoke(Object obj, Object[] args)
        throws IllegalArgumentException, InvocationTargetException
    {
    	/* We can't inflate methods belonging to vm-anonymous classes because that kind of class can't be referred to by name, hence can't be found from the generated bytecode */
    	if (++numInvocations > ReflectionFactory.inflationThreshold()
                && !ReflectUtil.isVMAnonymousClass(method.getDeclaringClass())) {
            MethodAccessorImpl acc = (MethodAccessorImpl)
                new MethodAccessorGenerator().
                    generateMethod(method.getDeclaringClass(),
                                   method.getName(),
                                   method.getParameterTypes(),
                                   method.getReturnType(),
                                   method.getExceptionTypes(),
                                   method.getModifiers());
            parent.setDelegate(acc);
        }
        return invoke0(method, obj, args);
    }
    void setParent(DelegatingMethodAccessorImpl parent) {
        this.parent = parent;
    }
    private static native Object invoke0(Method m, Object obj, Object[] args);
}
  • 每次NativeMethodAccessorImpl.invoke() 方法被呼叫時,程式呼叫計數器都會增加1, 看看是否超過閾值
  • 如果超過則呼叫MethodAccessorGenerator.generateMethod() 來生成Java版的MethodAccessor的實現類
  • 改變DelegatingMethodAccessorImpl所引用的MethodAccessorJava
  • 經由DelegatingMethodAccessorImpl.invoke() 呼叫到的就是Java版的實現

JVM層invoke0方法

  • invoke0方法是一個native方法,在HotSpot JVM裡呼叫JVM_InvokeMethod函式:
JNIEXPORT jobject JNICALL Java_sun_reflect_NativeMethodAccessorImpl_invoke0
(JNIEnv *env, jclass unused, jobject m, jobject obj, jobjectArray args)
{
    return JVM_InvokeMethod(env, m, obj, args);
}
  • openjdk/hotspot/src/share/vm/prims/jvm.cpp:
JVM_ENTRY(jobject, JVM_InvokeMethod(JNIEnv *env, jobject method, jobject obj, 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
  • 關鍵部分為Reflection::invoke_method: openjdk/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, receiver, override, ptypes, rtype, args, true, THREAD);
}

Java的物件模型 :klassoop

Java版的實現

  • JavaMethodAccessor的生成使用MethodAccessorGenerator實現
    Generator for sun.reflect.MethodAccessor and
    sun.reflect.ConstructorAccessor objects using bytecodes to
    implement reflection. A java.lang.reflect.Method or
    java.lang.reflect.Constructor object can delegate its invoke or
    newInstance method to an accessor using native code or to one
    generated by this class. (Methods and Constructors were merged
    together in this class to ensure maximum code sharing.)

運用了asm動態生成位元組碼技術 - sun.reflect.ClassFileAssembler

invoke總結

  • invoke方法的過程:
    在這裡插入圖片描述
  • MagicAccessorImpl:
    • 原本Java的安全機制使得不同類之間不是任意資訊都可見,但JDK裡面專門設了個MagicAccessorImpl標記類開了個後門來允許不同類之間資訊可以互相訪問,由JVM管理
/** <P> MagicAccessorImpl (named for parity with FieldAccessorImpl and
    others, not because it actually implements an interface) is a
    marker class in the hierarchy. All subclasses of this class are
    "magically" granted access by the VM to otherwise inaccessible
    fields and methods of other classes. It is used to hold the code
    for dynamically-generated FieldAccessorImpl and MethodAccessorImpl
    subclasses. (Use of the word "unsafe" was avoided in this class's
    name to avoid confusion with {@link sun.misc.Unsafe}.) </P>
    <P> The bug fix for 4486457 also necessitated disabling
    verification for this class and all subclasses, as opposed to just
    SerializationConstructorAccessorImpl and subclasses, to avoid
    having to indicate to the VM which of these dynamically-generated
    stub classes were known to be able to pass the verifier. </P>
    <P> Do not change the name of this class without also changing the
    VM's code. </P> */
class MagicAccessorImpl {
}
  • @CallerSensitive註解
Summary: Improve the security of the JDK’s method-handle implementation by replacing the existing
 hand-maintained list of caller-sensitive methods with a mechanism that accurately identifies
  such methods and allows their callers to be discovered reliably.
/**
 * A method annotated @CallerSensitive is sensitive to its calling class,
 * via {@link sun.reflect.Reflection#getCallerClass Reflection.getCallerClass},
 * or via some equivalent.
 *
 * @author John R. Rose
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({METHOD})
public @interface CallerSensitive {
}
  • @CallerSensitive註解修飾的方法從一開始就知道具體呼叫此方法的物件
    • 不用再經過一系列的檢查就能確定具體呼叫此方法的物件
    • 實際上是呼叫sun.reflect.Reflection.getCallerClass方法
  • Reflection類位於呼叫棧中的0幀位置
    • sun.reflect.Reflection.getCallerClass() 方法返回撥用棧中從0幀開始的第x幀中的類例項
    • 該方法提供的機制可用於確定呼叫者類,從而實現"感知呼叫者(Caller Sensitive)"的行為
    • 即允許應用程式根據呼叫類或呼叫棧中的其它類來改變其自身的行為

反射注意點

  • 反射會額外消耗系統資源,如果不需要動態地建立一個物件,就不要使用反射
  • 反射呼叫方法時可以忽略許可權檢查.可能會破壞封裝性而導致安全問題

相關文章