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

aqiandao發表於2016-10-20


  昨天在群裡跟大家討論了下java反射呼叫可變引數的問題,這個問題起因是我們需要反射呼叫另一個部門提供的方法,我同事說java不能反射呼叫可變引數的方法,於是我寫了個demo證明了他這個觀點的錯誤。但是測試過程中,有一點我不明白,就是反射呼叫可變引數的方法時,為什麼一定要保證傳入的引數陣列長度為1,在群裡跟大家討論了很多,沒有得到確切的答案,參照網上大牛寫的東西和我自己跟原始碼的過程,記錄如下:

  1.兩個類,一個父類,一個子類

  [java] view plain copy print?

  package com.reflect.test;

  public class BaseObject {

  public void getObjectName(){

  System.out.println("BaseObject");

  }

  }

  [java] view plain copy print?

  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.測試類,主要測試過載方法的呼叫、可變引數方法的呼叫、定參方法的呼叫

  [java] view plain copy print?

  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,然後做是否有許可權訪問的校驗,校驗之後快取一次,以便下次如果還是這個類來呼叫就不用去做校驗了,直接用上次的結果。

  [java] view plain copy print?

  @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方法例項化。程式碼片段如下;

  [java] view plain copy print?

  //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了。程式碼片段如下:

  [java] view plain copy print?

  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方法:

  程式碼片段如下:

  [java] view plain copy print?

  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] view plain copy print?

  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的實現:

  [java] view plain copy print?

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

  http://bj.bqqm.com/licai/z746373.htm

  http://bj.bqqm.com/licai/z746380.htm

  http://bj.bqqm.com/licai/z746385.htm

  http://blog.chinaunix.net/uid-30065054-id-5753598.html

  http://blog.chinaunix.net/uid-30065054-id-5753619.html

  http://blog.csdn.net/sinat_36394053/article/details/52857375

  http://blog.itpub.net/30065054/viewspace-2126715/

  http://blog.itpub.net/30065054/viewspace-2126747/

  http://run.gmw.cn/blog-1873077-177613.html

  

  

  

  

  

  

  

  

  

  

  

  

  

  

  

  

  

  

  

  

  

  

  

  

  

  

  

  

  

  

  

  

  

  

  

  

  

  

  

  

  http://www.cnblogs.com/huqingchen/p/5979936.html

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/30065054/viewspace-2126796/,如需轉載,請註明出處,否則將追究法律責任。

相關文章