java反射機制之Method invoke執行呼叫方法例子

yaerfeng發表於2016-10-17

原文: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會做強制型別轉換再進行方法呼叫,但父類強制轉化成子類的的時候就會報錯型別不匹配錯誤了,所以如果變數的引用宣告是父但實際指向的物件是子,那麼這種呼叫也是可以的。

相關文章