java反射機制之Method invoke執行呼叫方法例子
原文:java反射機制之Method invoke執行呼叫方法例子
昨天在群裡跟大家討論了下java反射呼叫可變引數的問題,這個問題起因是我們需要反射呼叫另一個部門提供的方法,我同事說java不能反射呼叫可變引數的方法,於是我寫了個demo證明了他這個觀點的錯誤。但是測試過程中,有一點我不明白,就是反射呼叫可變引數的方法時,為什麼一定要保證傳入的引數陣列長度為1,在群裡跟大家討論了很多,沒有得到確切的答案,參照網上大牛寫的東西和我自己跟原始碼的過程,記錄如下:
1.兩個類,一個父類,一個子類
package com.reflect.test;
public class BaseObject {
public void getObjectName(){
System.out.println("BaseObject");
}
}
package com.reflect.test;
public class SubObject extends BaseObject{
@Override
public void getObjectName() {
System.out.println("SubObject");
}
public void getParamsLength(String...params){
System.out.println("param's length is:"+params.length);
}
public void getParamsLength(String param1,String param2){
System.out.println(param1 + "-" + param2);
}
}
2.測試類,主要測試過載方法的呼叫、可變引數方法的呼叫、定參方法的呼叫
package com.reflect.test;
import java.lang.reflect.Method;
public class ReflectTest {
private static final String BASE_OBJECT_PATH = "com.reflect.test.BaseObject";
private static final String SUB_OBJECT_PATH = "com.reflect.test.SubObject";
public static void main(String[] args) throws Exception{
Class<?> bClazz = Class.forName(BASE_OBJECT_PATH);
Class<?> sClazz = Class.forName(SUB_OBJECT_PATH);
Object bObj = bClazz.newInstance();//父類例項
Object sObj = sClazz.newInstance();//子類例項
//1.反射呼叫子類父類的過載方法
//多型+動態繫結
Method bMethod = bClazz.getDeclaredMethod("getObjectName");
bMethod.invoke(bObj);//父類的bMethod呼叫父類的getObjectName()
bMethod.invoke(sObj);//父類的bMethod呼叫子類的getObjectName();
Method sMethod = sClazz.getDeclaredMethod("getObjectName");
//不符合多型和動態繫結
//sMethod.invoke(bObj);//sMethod呼叫父類的getObjectName(),會報錯:java.lang.IllegalArgumentException: object is not an instance of declaring class
sMethod.invoke(sObj);
//2.反射呼叫可變引數的方法
Method changeMethod = sClazz.getDeclaredMethod("getParamsLength", String[].class);
//可變引數必須這樣封裝,因為java反射內部實現做了引數個數為1的判斷,如果引數長度不為1,則會丟擲異常
String[] strParams = {"a","b","c"};
Object[] cParams = {strParams};
changeMethod.invoke(sObj, cParams);
//3.反射呼叫固定長度引數的方法
Method unChangeMethod1 = sClazz.getDeclaredMethod("getParamsLength", String.class,String.class);
unChangeMethod1.invoke(sObj, "Hello","Java");
//也可以寫成這樣
Class<?>[] clazzs = {String.class,String.class};
Method unChangeMethod2 = sClazz.getDeclaredMethod("getParamsLength", clazzs);
unChangeMethod2.invoke(sObj, "Hello","Java");
//下面的這種呼叫形式也是可以的,不過會報警告
//String[] params1 = {"Hello","Java"};
//unChangeMethod1.invoke(sObj, params1);
}
}
下面是JDK裡面Method 的invoke方法的原始碼
從程式碼中可以看出,先檢查 AccessibleObject的override屬性是否為true(override屬性預設為false)。AccessibleObject是Method,Field,Constructor的父類,可呼叫setAccessible方法改變,如果設定為true,則表示可以忽略訪問許可權的限制,直接呼叫。
如果不是ture,則要進行訪問許可權檢測。用Reflection的quickCheckMemberAccess方法先檢查是不是public的,如果不是再用Reflection.getCallerClass()方法獲得到呼叫這個方法的Class,然後做是否有許可權訪問的校驗,校驗之後快取一次,以便下次如果還是這個類來呼叫就不用去做校驗了,直接用上次的結果。
@CallerSensitive
public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
// Until there is hotspot @CallerSensitive support
// can't call Reflection.getCallerClass() here
// Workaround for now: add a frame getCallerClass to
// make the caller at stack depth 2
Class<?> caller = getCallerClass();
checkAccess(caller, clazz, obj, modifiers);
}
}
MethodAccessor ma = methodAccessor; // read volatile
if (ma == null) {
ma = acquireMethodAccessor();
}
return ma.invoke(obj, args);
}
//驗證的程式碼,securityCheckCache就是JDK做的快取
volatile Object securityCheckCache;
void checkAccess(Class<?> caller, Class<?> clazz, Object obj, int modifiers)
throws IllegalAccessException
{
if (caller == clazz) { // quick check
return; // ACCESS IS OK
}
Object cache = securityCheckCache; // read volatile
Class<?> targetClass = clazz;
if (obj != null
&& Modifier.isProtected(modifiers)
&& ((targetClass = obj.getClass()) != clazz)) {
// Must match a 2-list of { caller, targetClass }.
if (cache instanceof Class[]) {
Class<?>[] cache2 = (Class<?>[]) cache;
if (cache2[1] == targetClass &&
cache2[0] == caller) {
return; // ACCESS IS OK
}
// (Test cache[1] first since range check for [1]
// subsumes range check for [0].)
}
} else if (cache == caller) {
// Non-protected case (or obj.class == this.clazz).
return; // ACCESS IS OK
}
// If no return, fall through to the slow path.
slowCheckMemberAccess(caller, clazz, obj, modifiers, targetClass);
}
然後就是呼叫MethodAccessor的invoke方法了。
呼叫MethodAccessor的invoke方法。每個Method物件包含一個root物件,root物件裡持有一個MethodAccessor物件。這個物件由ReflectionFactory方法生成,ReflectionFactory物件在Method類中是static final的由native方法例項化。程式碼片段如下;
//Method類中的程式碼片段,生成MethodAccessor
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;
}
// reflectionFactory在父類AccessibleObject中定義,程式碼片段如下:
static final ReflectionFactory reflectionFactory =
AccessController.doPrivileged(
new sun.reflect.ReflectionFactory.GetReflectionFactoryAction());
ReflectionFactory生成MethodAccessor:如果noInflation的屬性為true則直接返回MethodAccessorGenerator建立的一個MethodAccessor,否則返回DelegatingMethodAccessorImpl,並將他與一個NativeMethodAccessorImpl互相引用。但DelegatingMethodAccessorImpl執行invoke方法的時候又委託給NativeMethodAccessorImpl了。程式碼片段如下:
public MethodAccessor newMethodAccessor(Method paramMethod) {
checkInitted();
if (noInflation) {
return new MethodAccessorGenerator().generateMethod(paramMethod.getDeclaringClass(), paramMethod.getName(), paramMethod.getParameterTypes(), paramMethod.getReturnType(), paramMethod.getExceptionTypes(), paramMethod.getModifiers());
}
NativeMethodAccessorImpl localNativeMethodAccessorImpl = new NativeMethodAccessorImpl(paramMethod);
DelegatingMethodAccessorImpl localDelegatingMethodAccessorImpl = new DelegatingMethodAccessorImpl(localNativeMethodAccessorImpl);
localNativeMethodAccessorImpl.setParent(localDelegatingMethodAccessorImpl);
return localDelegatingMethodAccessorImpl;
}
MethodAccessor實現有兩個版本,一個是Java實現的,另一個是native code實現的。Java實現的版本在初始化時需要較多時間,但長久來說效能較好;native版本正好相反,啟動時相對較快,但執行時間長了之後速度就比不過Java版了。這是HotSpot的優化方式帶來的效能特性,同時也是許多虛擬機器的共同點:跨越native邊界會對優化有阻礙作用,它就像個黑箱一樣讓虛擬機器難以分析也將其內聯,於是執行時間長了之後反而是託管版本的程式碼更快些。 為了權衡兩個版本的效能,Sun的JDK使用了“inflation”的技巧:讓Java方法在被反射呼叫時,開頭若干次使用native版,等反射呼叫次數超過閾值時則生成一個專用的MethodAccessor實現類,生成其中的invoke()方法的位元組碼,以後對該Java方法的反射呼叫就會使用Java版。
看下NativeMethodAccessorImpl 中的invoke方法:
程式碼片段如下:
package sun.reflect;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
class NativeMethodAccessorImpl extends MethodAccessorImpl
{
private Method method;
private DelegatingMethodAccessorImpl parent;
private int numInvocations;
NativeMethodAccessorImpl(Method paramMethod)
{
this.method = paramMethod;
}
public Object invoke(Object paramObject, Object[] paramArrayOfObject)
throws IllegalArgumentException, InvocationTargetException
{
if (++this.numInvocations > ReflectionFactory.inflationThreshold()) {
MethodAccessorImpl localMethodAccessorImpl = (MethodAccessorImpl)new MethodAccessorGenerator().generateMethod(this.method.getDeclaringClass(), this.method.getName(), this.method.getParameterTypes(), this.method.getReturnType(), this.method.getExceptionTypes(), this.method.getModifiers());
this.parent.setDelegate(localMethodAccessorImpl);
}
return invoke0(this.method, paramObject, paramArrayOfObject);
}
void setParent(DelegatingMethodAccessorImpl paramDelegatingMethodAccessorImpl) {
this.parent = paramDelegatingMethodAccessorImpl;
}
private static native Object invoke0(Method paramMethod, Object paramObject, Object[] paramArrayOfObject);
}
呼叫natiave方法invoke0執行方法呼叫.
注意這裡有一個計數器numInvocations,每呼叫一次方法+1,當比 ReflectionFactory.inflationThreshold(15)大的時候,用MethodAccessorGenerator建立一個MethodAccessor,並把之前的DelegatingMethodAccessorImpl引用替換為現在新建立的。下一次DelegatingMethodAccessorImpl就不會再交給NativeMethodAccessorImpl執行了,而是交給新生成的java位元組碼的MethodAccessor
每次NativeMethodAccessorImpl.invoke()方法被呼叫時,都會增加一個呼叫次數計數器,看超過閾值沒有;一旦超過,則呼叫MethodAccessorGenerator.generateMethod()來生成Java版的MethodAccessor的實現類,並且改變DelegatingMethodAccessorImpl所引用的MethodAccessor為Java版。後續經由DelegatingMethodAccessorImpl.invoke()呼叫到的就是Java版的實現了。
注意到關鍵的invoke0()方法是個native方法。它在HotSpot VM裡是由JVM_InvokeMethod()函式所支援的,是用C寫的
為了驗證這個結論,我故意寫出一個非法引數,迴圈呼叫16次並catch下異常,結果如下:從結果中看出,前15次都是呼叫NativeMethodAccessorImpl,第16次開始就是呼叫DelegatingMethodAccessorImpl了。
java.lang.IllegalArgumentException: wrong number of arguments
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at com.reflect.test.ReflectTest.main(ReflectTest.java:44)
java.lang.IllegalArgumentException: wrong number of arguments
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at com.reflect.test.ReflectTest.main(ReflectTest.java:44)
java.lang.IllegalArgumentException: wrong number of arguments
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at com.reflect.test.ReflectTest.main(ReflectTest.java:44)
java.lang.IllegalArgumentException: wrong number of arguments
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at com.reflect.test.ReflectTest.main(ReflectTest.java:44)
java.lang.IllegalArgumentException: wrong number of arguments
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at com.reflect.test.ReflectTest.main(ReflectTest.java:44)
java.lang.IllegalArgumentException: wrong number of arguments
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at com.reflect.test.ReflectTest.main(ReflectTest.java:44)
java.lang.IllegalArgumentException: wrong number of arguments
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at com.reflect.test.ReflectTest.main(ReflectTest.java:44)
java.lang.IllegalArgumentException: wrong number of arguments
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at com.reflect.test.ReflectTest.main(ReflectTest.java:44)
java.lang.IllegalArgumentException: wrong number of arguments
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at com.reflect.test.ReflectTest.main(ReflectTest.java:44)
java.lang.IllegalArgumentException: wrong number of arguments
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at com.reflect.test.ReflectTest.main(ReflectTest.java:44)
java.lang.IllegalArgumentException: wrong number of arguments
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at com.reflect.test.ReflectTest.main(ReflectTest.java:44)
java.lang.IllegalArgumentException: wrong number of arguments
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at com.reflect.test.ReflectTest.main(ReflectTest.java:44)
java.lang.IllegalArgumentException: wrong number of arguments
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at com.reflect.test.ReflectTest.main(ReflectTest.java:44)
java.lang.IllegalArgumentException: wrong number of arguments
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at com.reflect.test.ReflectTest.main(ReflectTest.java:44)
java.lang.IllegalArgumentException: wrong number of arguments
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at com.reflect.test.ReflectTest.main(ReflectTest.java:44)
java.lang.IllegalArgumentException
at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at com.reflect.test.ReflectTest.main(ReflectTest.java:44)
下面看看java版的DelegatingMethodAccessorImpl的實現:
package sun.reflect;
import java.lang.reflect.InvocationTargetException;
class DelegatingMethodAccessorImpl extends MethodAccessorImpl
{
private MethodAccessorImpl delegate;
DelegatingMethodAccessorImpl(MethodAccessorImpl paramMethodAccessorImpl)
{
setDelegate(paramMethodAccessorImpl);
}
public Object invoke(Object paramObject, Object[] paramArrayOfObject)
throws IllegalArgumentException, InvocationTargetException
{
return this.delegate.invoke(paramObject, paramArrayOfObject);
}
void setDelegate(MethodAccessorImpl paramMethodAccessorImpl) {
this.delegate = paramMethodAccessorImpl;
}
package sun.reflect;
public class GeneratedMethodAccessor1 extends MethodAccessorImpl {
public GeneratedMethodAccessor1() {
super();
}
public Object invoke(Object obj, Object[] args)
throws IllegalArgumentException, InvocationTargetException {
// prepare the target and parameters
if (obj == null) throw new NullPointerException();
try {
A target = (A) obj;
if (args.length != 1) throw new IllegalArgumentException();
String arg0 = (String) args[0];
} catch (ClassCastException e) {
throw new IllegalArgumentException(e.toString());
} catch (NullPointerException e) {
throw new IllegalArgumentException(e.toString());
}
// make the invocation
try {
target.foo(arg0);
} catch (Throwable t) {
throw new InvocationTargetException(t);
}
}
}
if (args.length != 1) throw new IllegalArgumentException();這一句就能解釋我之前的疑問了,這塊會判斷引數陣列的長度,如果長度不等於1,就會丟擲非法引數的異常。
而且MethodAccessor會做強制型別轉換再進行方法呼叫,但父類強制轉化成子類的的時候就會報錯型別不匹配錯誤了,所以如果變數的引用宣告是父但實際指向的物件是子,那麼這種呼叫也是可以的。
相關文章
- 簡述java反射機制之Method invoke執行呼叫方法例子Java反射
- 用反射呼叫Method類的invoke方法反射
- method.invoke(...)反射點反射
- 借用Java的Invoke反射,帶呼叫其他類的方法Java反射
- 深入理解Java中的反射機制和使用原理!詳細解析invoke方法的執行和使用Java反射
- Java中的反射機制(二) 一個利用反射進行物件拷貝的例子Java反射物件
- java 反射之操作靜態MethodJava反射
- Java基礎之反射機制(續)Java反射
- Java反射機制Java反射
- Java反射機制demo(五)—獲得並呼叫一個類中的方法Java反射
- java對過反射呼叫方法Java反射
- Java核心反射機制Java反射
- java利器——反射機制Java反射
- Java的反射機制Java反射
- Java反射機制(轉)Java反射
- Java反射機制研究Java反射
- java多執行緒系列之future機制Java執行緒
- Java反射機制(得到所有的構造方法)Java反射構造方法
- java框架基礎技術之--------反射機制Java框架反射
- java的執行機制Java
- java反射構建物件和方法的反射呼叫Java反射物件
- Java安全基礎之Java反射機制和ClassLoader類載入機制Java反射
- java反射小例子Java反射
- Java反射機制那些事Java反射
- Java 中的 反射機制Java反射
- Java反射機制簡答Java反射
- 說說 Java 反射機制Java反射
- JAVA(五)反射機制/AnnotationJava反射
- Java 反射機制分析指南Java反射
- Java 反射機制詳解Java反射
- Java的執行機制分析!Java
- java程式的執行機制Java
- 淺聊java執行機制Java
- 呼叫JavaCalls::call()方法來執行Java方法Java
- Java中的類反射機制Java反射
- Java筆記-反射機制(一)Java筆記反射
- java進階(41)--反射機制Java反射
- 淺析java的反射機制Java反射