昨天在群里跟大家讨论了下java反射调用可变参数的问题,这个问题起因是我们需要反射调用另一个部门提供的方法,我同事说java不能反射调用可变参数的方法,于是我写了个demo证明了他这个观点的错误。但是测试过程中,有一点我不明白,就是反射调用可变参数的方法时,为什么一定要保证传入的参数数组长度为1,在群里跟大家讨论了很多,没有得到确切的答案,参照网上大牛写的东西和我自己跟源码的过程,记录如下:
1.两个类,一个父类,一个子类
1 |
package com.reflect.test; |
3 |
public class BaseObject
{ |
5 |
public void getObjectName(){ |
6 |
System.out.println("BaseObject"); |
01 |
package com.reflect.test; |
03 |
public class SubObject extends BaseObject{ |
05 |
public void getObjectName()
{ |
06 |
System.out.println("SubObject"); |
08 |
public void getParamsLength(String...params){ |
09 |
System.out.println("param's
length is:"+params.length); |
11 |
public void getParamsLength(String
param1,String param2){ |
12 |
System.out.println(param1
+ "-" +
param2); |
2.测试类,主要测试重载方法的调用、可变参数方法的调用、定参方法的调用
01 |
package com.reflect.test; |
03 |
import java.lang.reflect.Method; |
05 |
public class ReflectTest
{ |
07 |
private static final String
BASE_OBJECT_PATH = "com.reflect.test.BaseObject"; |
08 |
private static final String
SUB_OBJECT_PATH = "com.reflect.test.SubObject"; |
10 |
public static void main(String[]
args) throws Exception{ |
12 |
Class<?>
bClazz = Class.forName(BASE_OBJECT_PATH); |
13 |
Class<?>
sClazz = Class.forName(SUB_OBJECT_PATH); |
15 |
Object
bObj = bClazz.newInstance(); |
16 |
Object
sObj = sClazz.newInstance(); |
20 |
Method
bMethod = bClazz.getDeclaredMethod("getObjectName"); |
24 |
Method
sMethod = sClazz.getDeclaredMethod("getObjectName"); |
30 |
Method
changeMethod = sClazz.getDeclaredMethod("getParamsLength",
String[].class); |
32 |
String[]
strParams = {"a","b","c"}; |
33 |
Object[]
cParams = {strParams}; |
34 |
changeMethod.invoke(sObj,
cParams); |
37 |
Method
unChangeMethod1 = sClazz.getDeclaredMethod("getParamsLength",
String.class,String.class); |
38 |
unChangeMethod1.invoke(sObj, "Hello","Java"); |
40 |
Class<?>[]
clazzs = {String.class,String.class}; |
41 |
Method
unChangeMethod2 = sClazz.getDeclaredMethod("getParamsLength",
clazzs); |
42 |
unChangeMethod2.invoke(sObj, "Hello","Java"); |
下面是JDK里面Method 的invoke方法的源码
从代码中可以看出,先检查 AccessibleObject的override属性是否为true(override属性默认为false)。AccessibleObject是Method,Field,Constructor的父类,可调用setAccessible方法改变,如果设置为true,则表示可以忽略访问权限的限制,直接调用。
如果不是ture,则要进行访问权限检测。用Reflection的quickCheckMemberAccess方法先检查是不是public的,如果不是再用Reflection.getCallerClass()方法获得到调用这个方法的Class,然后做是否有权限访问的校验,校验之后缓存一次,以便下次如果还是这个类来调用就不用去做校验了,直接用上次的结果。
02 |
public Object
invoke(Object obj, Object... args) |
03 |
throws IllegalAccessException,
IllegalArgumentException, |
04 |
InvocationTargetException |
07 |
if (!Reflection.quickCheckMemberAccess(clazz,
modifiers)) { |
12 |
Class<?>
caller = getCallerClass(); |
13 |
checkAccess(caller,
clazz, obj, modifiers); |
16 |
MethodAccessor
ma = methodAccessor; |
18 |
ma
= acquireMethodAccessor(); |
20 |
return ma.invoke(obj,
args); |
24 |
volatile Object
securityCheckCache; |
26 |
void checkAccess(Class<?>
caller, Class<?> clazz, Object obj, int modifiers) |
27 |
throws IllegalAccessException |
29 |
if (caller
== clazz) { |
32 |
Object
cache = securityCheckCache; |
33 |
Class<?>
targetClass = clazz; |
35 |
&&
Modifier.isProtected(modifiers) |
36 |
&&
((targetClass = obj.getClass()) != clazz)) { |
38 |
if (cache instanceof Class[])
{ |
39 |
Class<?>[]
cache2 = (Class<?>[]) cache; |
40 |
if (cache2[1]
== targetClass && |
41 |
cache2[0]
== caller) { |
47 |
} else if (cache
== caller) { |
53 |
slowCheckMemberAccess(caller,
clazz, obj, modifiers, targetClass); |
然后就是调用MethodAccessor的invoke方法了。
调用MethodAccessor的invoke方法。每个Method对象包含一个root对象,root对象里持有一个MethodAccessor对象。这个对象由ReflectionFactory方法生成,ReflectionFactory对象在Method类中是static final的由native方法实例化。代码片段如下;
02 |
private volatile MethodAccessor
methodAccessor; |
04 |
private MethodAccessor
acquireMethodAccessor() { |
07 |
MethodAccessor
tmp = null; |
08 |
if (root
!= null)
tmp = root.getMethodAccessor(); |
13 |
tmp
= reflectionFactory.newMethodAccessor(this); |
14 |
setMethodAccessor(tmp); |
21 |
static final ReflectionFactory
reflectionFactory = |
22 |
AccessController.doPrivileged( |
23 |
new sun.reflect.ReflectionFactory.GetReflectionFactoryAction()); |
ReflectionFactory生成MethodAccessor:如果noInflation的属性为true则直接返回MethodAccessorGenerator创建的一个MethodAccessor,否则返回DelegatingMethodAccessorImpl,并将他与一个NativeMethodAccessorImpl互相引用。但DelegatingMethodAccessorImpl执行invoke方法的时候又委托给NativeMethodAccessorImpl了。代码片段如下:
01 |
public MethodAccessor
newMethodAccessor(Method paramMethod) { |
05 |
return new MethodAccessorGenerator().generateMethod(paramMethod.getDeclaringClass(),
paramMethod.getName(), paramMethod.getParameterTypes(), paramMethod.getReturnType(), paramMethod.getExceptionTypes(), paramMethod.getModifiers()); |
08 |
NativeMethodAccessorImpl
localNativeMethodAccessorImpl = new NativeMethodAccessorImpl(paramMethod); |
10 |
DelegatingMethodAccessorImpl
localDelegatingMethodAccessorImpl = new DelegatingMethodAccessorImpl(localNativeMethodAccessorImpl); |
12 |
localNativeMethodAccessorImpl.setParent(localDelegatingMethodAccessorImpl); |
13 |
return localDelegatingMethodAccessorImpl; |
MethodAccessor实现有两个版本,一个是Java实现的,另一个是native code实现的。Java实现的版本在初始化时需要较多时间,但长久来说性能较好;native版本正好相反,启动时相对较快,但运行时间长了之后速度就比不过Java版了。这是HotSpot的优化方式带来的性能特性,同时也是许多虚拟机的共同点:跨越native边界会对优化有阻碍作用,它就像个黑箱一样让虚拟机难以分析也将其内联,于是运行时间长了之后反而是托管版本的代码更快些。 为了权衡两个版本的性能,Sun的JDK使用了“inflation”的技巧:让Java方法在被反射调用时,开头若干次使用native版,等反射调用次数超过阈值时则生成一个专用的MethodAccessor实现类,生成其中的invoke()方法的字节码,以后对该Java方法的反射调用就会使用Java版。
看下NativeMethodAccessorImpl 中的invoke方法:
代码片段如下:
03 |
import java.lang.reflect.InvocationTargetException; |
04 |
import java.lang.reflect.Method; |
06 |
class NativeMethodAccessorImpl extends MethodAccessorImpl |
08 |
private Method
method; |
09 |
private DelegatingMethodAccessorImpl
parent; |
10 |
private int numInvocations; |
12 |
NativeMethodAccessorImpl(Method
paramMethod) |
14 |
this.method
= paramMethod; |
17 |
public Object
invoke(Object paramObject, Object[] paramArrayOfObject) |
18 |
throws IllegalArgumentException,
InvocationTargetException |
20 |
if (++this.numInvocations
> ReflectionFactory.inflationThreshold()) { |
21 |
MethodAccessorImpl
localMethodAccessorImpl = (MethodAccessorImpl)new MethodAccessorGenerator().generateMethod(this.method.getDeclaringClass(), this.method.getName(),this.method.getParameterTypes(), this.method.getReturnType(), this.method.getExceptionTypes(), this.method.getModifiers()); |
23 |
this.parent.setDelegate(localMethodAccessorImpl); |
26 |
return invoke0(this.method,
paramObject, paramArrayOfObject); |
29 |
void setParent(DelegatingMethodAccessorImpl
paramDelegatingMethodAccessorImpl) { |
30 |
this.parent
= paramDelegatingMethodAccessorImpl; |
33 |
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了。
01 |
java.lang.IllegalArgumentException:
wrong number of arguments |
02 |
at
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) |
03 |
at
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) |
04 |
at
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) |
05 |
at
java.lang.reflect.Method.invoke(Method.java:606) |
06 |
at
com.reflect.test.ReflectTest.main(ReflectTest.java:44) |
07 |
java.lang.IllegalArgumentException:
wrong number of arguments |
08 |
at
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) |
09 |
at
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) |
10 |
at
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) |
11 |
at
java.lang.reflect.Method.invoke(Method.java:606) |
12 |
at
com.reflect.test.ReflectTest.main(ReflectTest.java:44) |
13 |
java.lang.IllegalArgumentException:
wrong number of arguments |
14 |
at
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) |
15 |
at
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) |
16 |
at
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) |
17 |
at
java.lang.reflect.Method.invoke(Method.java:606) |
18 |
at
com.reflect.test.ReflectTest.main(ReflectTest.java:44) |
19 |
java.lang.IllegalArgumentException:
wrong number of arguments |
20 |
at
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) |
21 |
at
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) |
22 |
at
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) |
23 |
at
java.lang.reflect.Method.invoke(Method.java:606) |
24 |
at
com.reflect.test.ReflectTest.main(ReflectTest.java:44) |
25 |
java.lang.IllegalArgumentException:
wrong number of arguments |
26 |
at
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) |
27 |
at
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) |
28 |
at
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) |
29 |
at
java.lang.reflect.Method.invoke(Method.java:606) |
30 |
at
com.reflect.test.ReflectTest.main(ReflectTest.java:44) |
31 |
java.lang.IllegalArgumentException:
wrong number of arguments |
32 |
at
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) |
33 |
at
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) |
34 |
at
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) |
35 |
at
java.lang.reflect.Method.invoke(Method.java:606) |
36 |
at
com.reflect.test.ReflectTest.main(ReflectTest.java:44) |
37 |
java.lang.IllegalArgumentException:
wrong number of arguments |
38 |
at
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) |
39 |
at
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) |
40 |
at
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) |
41 |
at
java.lang.reflect.Method.invoke(Method.java:606) |
42 |
at
com.reflect.test.ReflectTest.main(ReflectTest.java:44) |
43 |
java.lang.IllegalArgumentException:
wrong number of arguments |
44 |
at
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) |
45 |
at
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) |
46 |
at
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) |
47 |
at
java.lang.reflect.Method.invoke(Method.java:606) |
48 |
at
com.reflect.test.ReflectTest.main(ReflectTest.java:44) |
49 |
java.lang.IllegalArgumentException:
wrong number of arguments |
50 |
at
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) |
51 |
at
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) |
52 |
at
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) |
53 |
at
java.lang.reflect.Method.invoke(Method.java:606) |
54 |
at
com.reflect.test.ReflectTest.main(ReflectTest.java:44) |
55 |
java.lang.IllegalArgumentException:
wrong number of arguments |
56 |
at
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) |
57 |
at
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) |
58 |
at
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) |
59 |
at
java.lang.reflect.Method.invoke(Method.java:606) |
60 |
at
com.reflect.test.ReflectTest.main(ReflectTest.java:44) |
61 |
java.lang.IllegalArgumentException:
wrong number of arguments |
62 |
at
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) |
63 |
at
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) |
64 |
at
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) |
65 |
at
java.lang.reflect.Method.invoke(Method.java:606) |
66 |
at
com.reflect.test.ReflectTest.main(ReflectTest.java:44) |
67 |
java.lang.IllegalArgumentException:
wrong number of arguments |
68 |
at
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) |
69 |
at
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) |
70 |
at
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) |
71 |
at
java.lang.reflect.Method.invoke(Method.java:606) |
72 |
at
com.reflect.test.ReflectTest.main(ReflectTest.java:44) |
73 |
java.lang.IllegalArgumentException:
wrong number of arguments |
74 |
at
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) |
75 |
at
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) |
76 |
at
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) |
77 |
at
java.lang.reflect.Method.invoke(Method.java:606) |
78 |
at
com.reflect.test.ReflectTest.main(ReflectTest.java:44) |
79 |
java.lang.IllegalArgumentException:
wrong number of arguments |
80 |
at
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) |
81 |
at
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) |
82 |
at
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) |
83 |
at
java.lang.reflect.Method.invoke(Method.java:606) |
84 |
at
com.reflect.test.ReflectTest.main(ReflectTest.java:44) |
85 |
java.lang.IllegalArgumentException:
wrong number of arguments |
86 |
at
sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) |
87 |
at
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) |
88 |
at
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) |
89 |
at
java.lang.reflect.Method.invoke(Method.java:606) |
90 |
at
com.reflect.test.ReflectTest.main(ReflectTest.java:44) |
91 |
java.lang.IllegalArgumentException |
92 |
at
sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source) |
93 |
at
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) |
94 |
at
java.lang.reflect.Method.invoke(Method.java:606) |
95 |
at
com.reflect.test.ReflectTest.main(ReflectTest.java:44) |
下面看看java版的DelegatingMethodAccessorImpl的实现:
03 |
import java.lang.reflect.InvocationTargetException; |
05 |
class DelegatingMethodAccessorImpl extends MethodAccessorImpl |
07 |
private MethodAccessorImpl
delegate; |
09 |
DelegatingMethodAccessorImpl(MethodAccessorImpl
paramMethodAccessorImpl) |
11 |
setDelegate(paramMethodAccessorImpl); |
14 |
public Object
invoke(Object paramObject, Object[] paramArrayOfObject) |
15 |
throws IllegalArgumentException,
InvocationTargetException |
17 |
return this.delegate.invoke(paramObject,
paramArrayOfObject); |
20 |
void setDelegate(MethodAccessorImpl
paramMethodAccessorImpl) { |
21 |
this.delegate
= paramMethodAccessorImpl; |
27 |
public class GeneratedMethodAccessor1 extends MethodAccessorImpl
{ |
28 |
public GeneratedMethodAccessor1()
{ |
32 |
public Object
invoke(Object obj, Object[] args) |
33 |
throws IllegalArgumentException,
InvocationTargetException { |
35 |
if (obj
== null) throw new NullPointerException(); |
38 |
if (args.length
!= 1) throw new IllegalArgumentException(); |
39 |
String
arg0 = (String) args[0]; |
40 |
} catch (ClassCastException
e) { |
41 |
throw new IllegalArgumentException(e.toString()); |
42 |
} catch (NullPointerException
e) { |
43 |
throw new IllegalArgumentException(e.toString()); |
48 |
} catch (Throwable
t) { |
49 |
throw new InvocationTargetException(t); |
if (args.length != 1) throw new IllegalArgumentException();这一句就能解释我之前的疑问了,这块会判断参数数组的长度,如果长度不等于1,就会抛出非法参数的异常。
而且MethodAccessor会做强制类型转换再进行方法调用,但父类强制转化成子类的的时候就会报错类型不匹配错误了,所以如果变量的引用声明是父但实际指向的对象是子,那么这种调用也是可以的。