java反射机制之Method invoke执行调用方法例子
来源:互联网 发布:sql语句统计女人数数量 编辑:程序博客网 时间:2024/05/23 12:01
原文: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{@Overridepublic 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 classsMethod.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类中的代码片段,生成MethodAccessorprivate 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 argumentsat 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 argumentsat 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 argumentsat 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 argumentsat 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 argumentsat 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 argumentsat 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 argumentsat 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 argumentsat 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 argumentsat 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 argumentsat 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 argumentsat 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 argumentsat 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 argumentsat 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 argumentsat 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 argumentsat 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.IllegalArgumentExceptionat 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执行调用方法例子
- java反射机制之Method invoke执行调用方法例子
- java反射机制之Method invoke执行调用方法例子
- 说下java反射机制之Method invoke执行调用方法例子
- java反射机制--Method.invoke方法
- 常用java反射机制 和 Method.invoke
- Java反射机制 以及 method.invoke运用
- java反射机制以及Method.invoke解释
- Java反射机制及Method.invoke详解
- Java反射机制及Method.invoke详解
- java中的反射--method的invoke方法
- java 反射中的method.invoke()方法详解
- java反射机制 + Method.invoke解释 getMethod + 反射理解
- java 反射机制 之 getMethod获取公有方法 getDeclaredMethod获取所有方法 然后invoke执行其所有方法
- java反射机制学习笔记与Method类invoke方法jvm底层优化方案
- java-Java反射机制及Method.invoke详解
- java反射Method中的如何调用任意方法,即invoke()的使用
- Android Studio中获取MD5、SHA1、SHA256的方法
- 大话存储-学习总结-5-虚拟化和存储集群
- 文章标题
- Filter(记录日志)
- HBase操作的基本函数
- java反射机制之Method invoke执行调用方法例子
- block 回调传值
- miniblink 16.10.17更新
- R语言读取数据(Practical Data Science with R 第二章)
- 姚庄电站推拉杆被推弯问题说明
- Ext Js MVC系列一 环境搭建和MVC框架整体认识
- C/C++字符串与数字的相互转化
- Java EE 项目 Servlet web.xml web-app节点头部信息,web-fragment.xml
- Perl,PHP,Python,Ruby概要及各自特点