0%

java反射2

前言:java反射2,本章主要学习总结invoke方法

简单实例

1
2
3
4
5
public class RealSubject{
public void doSomething() {
System.out.println("execute RealSubject method");
}
}

main.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class main {
public static void main(String[] args){
try{
获取Class对象
Class<?> realSubjectClass = RealSubject.class;

//创建RealSubject的实例
Object obj = realSubjectClass.newInstance();

//获取RealSubject类的doSomething方法
Method method = realSubjectClass.getMethod("doSomething");

//调用method对应的方法
method.invoke(obj);
} catch (Exception e) {
e.printStackTrace();
}
}
}

结果

1
execute RealSubject method

Method.invoke

功能:调用对象的方法

Method类图

image-20200102232102388

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@CallerSensitive
public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, obj, modifiers);
}
}
MethodAccessor ma = methodAccessor; // read volatile
if (ma == null) {
ma = acquireMethodAccessor();
}
return ma.invoke(obj, args);
}

权限检查

1
2
3
if (!override){

}

检查AccessibleObject的override属性的值。

AccessibleObject 类是Field类、Method类和Constructor类基类。

若override的值为true,表示忽略权限规则,调用方法时无需检查权限(也就是说可以调用任意的private方法,这违反了封装)

当override的值默认是false,表示需要权限调用规则,调用方法时需要检查权限
如果override属性为默认值false,则进行进一步的权限检查

1
2
3
4
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {


}

Method类 部分属性

1
2
3
4
5
public final class Method extends Executable {
private Class<?> clazz;
private int modifiers;
……
}

使用Reflection.quickCheckMemberAccess(clazz, modifiers)方法检查方法是否为public,其中clazz(clazz用于替代保留字class), modifiers均是Method变量;

Method.invoke()方法部分

1
2
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, obj, modifiers);

如果不是public方法,那么用Reflection.getCallerClass()方法可以得到调用者的类,这是一个native方法(源码在jvm.cpp中):

获取了这个Class对象caller后用checkAccess方法做一次权限校验,源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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);
}

首先执行一次quick check,一旦调用方法的caller == clazz则权限检查通过。
若未通过,则创建一个安全校验缓存,进行是否为protected访问修饰符等判断

AccessibleObject.slowCheckMemberAccess 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void slowCheckMemberAccess(Class<?> caller, Class<?> clazz, Object obj, int modifiers,
Class<?> targetClass)
throws IllegalAccessException
{
Reflection.ensureMemberAccess(caller, clazz, obj, modifiers);

// Success: Update the cache.
Object cache = ((targetClass == clazz)
? caller
: new Class<?>[] { caller, targetClass });

// Note: The two cache elements are not volatile,
// but they are effectively final. The Java memory model
// guarantees that the initializing stores for the cache
// elements will occur before the volatile write.
securityCheckCache = cache; // write volatile
}

Reflection.ensureMemberAccess 静态方法

1
2
3
4
5
6
7
8
9
public static void ensureMemberAccess(Class<?> var0, Class<?> var1, Object var2, int var3) throws IllegalAccessException {
if (var0 != null && var1 != null) {
if (!verifyMemberAccess(var0, var1, var2, var3)) {
throw new IllegalAccessException("Class " + var0.getName() + " can not access a member of class " + var1.getName() + " with modifiers \"" + Modifier.toString(var3) + "\"");
}
} else {
throw new InternalError();
}
}

MethodAccessor

Method.invoke()方法部分

1
2
3
4
MethodAccessor ma = methodAccessor;             // read volatile
if (ma == null) {
ma = acquireMethodAccessor();
}

Method 类 methodAccessor属性

1
2
3
4
public final class Method extends Executable {
private volatile MethodAccessor methodAccessor;
private Method root;
}

Method.invoke()实际上并不是自己实现的反射调用逻辑,而是委托给sun.reflect.MethodAccessor来处理。
首先要了解Method对象的基本构成,每个Java方法有且只有一个Method对象作为root,它相当于根对象,对用户不可见,如同Class对象,JVM也只加载并储存一个。当我们创建Method对象时,我们代码中获得的Method对象都相当于它的副本(或引用)。root对象持有一个MethodAccessor对象,所以所有获取到的Method对象都共享这一个MethodAccessor对象,因此必须保证它在内存中的可见性。root对象其声明及注释为:

1
2
3
public interface MethodAccessor {
Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException;
}
MethodAccessor接口的实现类

image-20200102232324044

java.lang.reflect.Method

查看acquireMethodAccessor方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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对象的newMethodAccessor方法生成一个MethodAccessor对象,并更新给root。之后调用通过root获取,然后调用MethodAccessor.invoke()完成反射调用,下面查看reflectionFactory和其调用的方法newMethodAccessor

java.lang.reflect.AccessibleObject
1
2
3
static final ReflectionFactory reflectionFactory =
AccessController.doPrivileged(
new sun.reflect.ReflectionFactory.GetReflectionFactoryAction());

reflectionFactory是AccessibleObject类的静态常量

sun.reflect.ReflectionFactory 部分源码

ReflectionFactory类是反射工厂类,负责复制Filed类、Method类、Constructor类(在JVM加载.class时创建),返回的对象用于判定三者的修饰符权限的,也就是实现了当方法是私有方法时,外部不能访问它的功能的,原理是当外部要访问这个私有方法时,会创建一个方法的MethodAccessor,并在里面进行相关的判断,判断如果是访问的权限那么允许访问者访问,否则拒绝抛出异常,其它两者也是一样的,而且不仅仅会判断修饰符的权限也会判断其属性的类型,方法的类型等。

1
2
3
4
5
6
7
8
9
10
11
12
13
public FieldAccessor newFieldAccessor(Field var1, boolean var2) {……}
public ConstructorAccessor newConstructorAccessor(Constructor<?> var1) {……}
public MethodAccessor newMethodAccessor(Method var1) {
checkInitted();
if (noInflation && !ReflectUtil.isVMAnonymousClass(var1.getDeclaringClass())) {
return (new MethodAccessorGenerator()).generateMethod(var1.getDeclaringClass(), var1.getName(), var1.getParameterTypes(), var1.getReturnType(), var1.getExceptionTypes(), var1.getModifiers());
} else {
NativeMethodAccessorImpl var2 = new NativeMethodAccessorImpl(var1);
DelegatingMethodAccessorImpl var3 = new DelegatingMethodAccessorImpl(var2);
var2.setParent(var3);
return var3;
}
}
checkInitted()方法源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
private static void checkInitted() {
if (!initted) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
if (System.out == null) {
return null;
} else {
String var1 = System.getProperty("sun.reflect.noInflation");
if (var1 != null && var1.equals("true")) {
ReflectionFactory.noInflation = true;
}

var1 = System.getProperty("sun.reflect.inflationThreshold");
if (var1 != null) {
try {
ReflectionFactory.inflationThreshold = Integer.parseInt(var1);
} catch (NumberFormatException var3) {
throw new RuntimeException("Unable to parse property sun.reflect.inflationThreshold", var3);
}
}

ReflectionFactory.initted = true;
return null;
}
}
});
}
}

checkInitted()方法检查是否初始化,没有则从配置项中读取配置并设置noInflation、inflationThreshold的值

noInflation、inflationThreshold的官方注释

PS:我用idea查看源码没有,下面注释来自jdk8文档

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// "Inflation" mechanism. Loading bytecodes to implement
// Method.invoke() and Constructor.newInstance() currently costs
// 3-4x more than an invocation via native code for the first
// invocation (though subsequent invocations have been benchmarked
// to be over 20x faster). Unfortunately this cost increases
// startup time for certain applications that use reflection
// intensively (but only once per class) to bootstrap themselves.
// To avoid this penalty we reuse the existing JVM entry points
// for the first few invocations of Methods and Constructors and
// then switch to the bytecode-based implementations.
//
// Package-private to be accessible to NativeMethodAccessorImpl
// and NativeConstructorAccessorImpl
private static boolean noInflation = false;
private static int inflationThreshold = 15;

渣翻

Inflation机制(膨胀机制)。初次加载字节码实现反射,使用method.invoke()和constructor.newInstance()时
比通过原生代码调用加载快3-4倍(尽管经过基准测试发现后续调用快20倍以上。但不幸的是,
某些频繁使用反射的程序的需要花费更多的启动时间
为了避免加载的惩罚,我们在第一次加载重用现有的JVM入口点对于方法和构造函数的前几个调用,之后切换到基于字节码的实现。

DelegatingMethodAccessorImpl 源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class DelegatingMethodAccessorImpl extends MethodAccessorImpl {
private MethodAccessorImpl delegate;

DelegatingMethodAccessorImpl(MethodAccessorImpl var1) {
this.setDelegate(var1);
}

public Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException {
return this.delegate.invoke(var1, var2);
}

void setDelegate(MethodAccessorImpl var1) {
this.delegate = var1;
}
}

在ReflectionFactory类的newMethodAccessor方法里,生成 NativeMethodAccessorImpl对象,并由DelegatingMethodAccessorImpl对象代理((这是运用了代理模式),返回DelegatingMethodAccessorImpl对象。所以invoke方法最终调用时是DelegatingMethodAccessorImpl.invoke,DelegatingMethodAccessorImpl.invoke调用NativeMethodAccessorImpl.invoke()

NativeMethodAccessorImpl源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class NativeMethodAccessorImpl extends MethodAccessorImpl {
private final Method method;
private DelegatingMethodAccessorImpl parent;
private int numInvocations;

NativeMethodAccessorImpl(Method var1) {
this.method = var1;
}

public Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException {
if (++this.numInvocations > ReflectionFactory.inflationThreshold() && !ReflectUtil.isVMAnonymousClass(this.method.getDeclaringClass())) {
MethodAccessorImpl var3 = (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(var3);
}

return invoke0(this.method, var1, var2);
}

void setParent(DelegatingMethodAccessorImpl var1) {
this.parent = var1;
}

private static native Object invoke0(Method var0, Object var1, Object[] var2);
}

NativeMethodAccessorImpl.invoke方法中,每次调用计数增加,并判断调用次数是否超过阀值(numInvocations)。一旦超过,则调用MethodAccessorGenerator.generateMethod()来生成Java版的MethodAccessor的实现类,并且改变DelegatingMethodAccessorImpl所引用的MethodAccessor为Java版,并将原来DelegatingMethodAccessorImpl对象中的delegate属性指向最新的MethodAccessor对象。注意到关键的invoke0()方法是个native方法,所以其代码就是由底层的C实现(代码在HotSpot VM中)

根据上面的注释,就清楚了实际的MethodAccessor实现有两个版本,一个是Java版本即使用MethodAccessorGenerator类实现,一个是native版本即使用native的invoke0方法实现,两者各有特点。初次启动时Method.invoke()和Constructor.newInstance()方法采用native方法要比Java方法快3-4倍,而启动后native方法又要消耗额外的性能而慢于Java方法。也就是说,Java实现的版本在初始化时需要较多时间,但长久来说性能较好;native版本正好相反,启动时相对较快,但运行时间长了之后速度就比不过Java版了。这是HotSpot的优化方式带来的性能特性,同时也是许多虚拟机的共同点:跨越native边界会对优化有阻碍作用,它就像个黑箱一样让虚拟机难以分析也将其内联,于是运行时间长了之后反而是托管版本的代码更快些。

为了尽可能地减少性能损耗,HotSpot JDK采用“inflation”的技巧:让Java方法在被反射调用时,开头若干次使用native版,等反射调用次数超过阈值时则生成一个专用的MethodAccessor实现类,生成其中的invoke()方法的字节码,以后对该Java方法的反射调用就会使用Java版本。 这项优化是从JDK 1.4开始的。

invoke0方法

invoke0方法是一个native方法,它在HotSpot JVM里调用JVM_InvokeMethod函数

1
2
3
4
5
JNIEXPORT jobject JNICALL Java_sun_reflect_NativeMethodAccessorImpl_invoke0
(JNIEnv *env, jclass unused, jobject m, jobject obj, jobjectArray args)
{
return JVM_InvokeMethod(env, m, obj, args);
}

openjdk/hotspot/src/share/vm/prims/jvm.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
JVM_ENTRY(jobject, JVM_InvokeMethod(JNIEnv *env, jobject method, jobject obj, jobjectArray args0))
JVMWrapper("JVM_InvokeMethod");
Handle method_handle;
if (thread->stack_available((address) &method_handle) >= JVMInvokeMethodSlack) {
method_handle = Handle(THREAD, JNIHandles::resolve(method));
Handle receiver(THREAD, JNIHandles::resolve(obj));
objArrayHandle args(THREAD, objArrayOop(JNIHandles::resolve(args0)));
oop result = Reflection::invoke_method(method_handle(), receiver, args, CHECK_NULL);
jobject res = JNIHandles::make_local(env, result);
if (JvmtiExport::should_post_vm_object_alloc()) {
oop ret_type = java_lang_reflect_Method::return_type(method_handle());
assert(ret_type != NULL, "sanity check: ret_type oop must not be NULL!");
if (java_lang_Class::is_primitive(ret_type)) {
// Only for primitive type vm allocates memory for java object.
// See box() method.
JvmtiExport::post_vm_object_alloc(JavaThread::current(), result);
}
}
return res;
} else {
THROW_0(vmSymbols::java_lang_StackOverflowError());
}
JVM_END

其关键部分为Reflection::invoke_method:
openjdk/hotspot/src/share/vm/runtime/reflection.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
oop Reflection::invoke_method(oop method_mirror, Handle receiver, objArrayHandle args, TRAPS) {
oop mirror = java_lang_reflect_Method::clazz(method_mirror);
int slot = java_lang_reflect_Method::slot(method_mirror);
bool override = java_lang_reflect_Method::override(method_mirror) != 0;
objArrayHandle ptypes(THREAD, objArrayOop(java_lang_reflect_Method::parameter_types(method_mirror)));
oop return_type_mirror = java_lang_reflect_Method::return_type(method_mirror);
BasicType rtype;
if (java_lang_Class::is_primitive(return_type_mirror)) {
rtype = basic_type_mirror_to_basic_type(return_type_mirror, CHECK_NULL);
} else {
rtype = T_OBJECT;
}
instanceKlassHandle klass(THREAD, java_lang_Class::as_Klass(mirror));
Method* m = klass->method_with_idnum(slot);
if (m == NULL) {
THROW_MSG_0(vmSymbols::java_lang_InternalError(), "invoke");
}
methodHandle method(THREAD, m);
return invoke(klass, method, receiver, override, ptypes, rtype, args, true, THREAD);
}

@CallerSensitive 注解

用于精确识别caller-sensitive方法并且保证这些方法的调用者可靠地被发现的一种机制,代替现存的手动维护的caller-sensitive方法表,提高JDK method-handler实现的安全性。

这个注解是为了堵住漏洞用的。曾经有黑客通过构造双重反射来提升权限,
原理是当时反射只检查固定深度的调用者的类,看它有没有特权,
例如固定看两层的调用者(getCallerClass(2))。如果我的类本来没足够
权限群访问某些信息,那我就可以通过双重反射去达到目的:反射相关
的类是有很高权限的,而在 我->反射1->反射2 这样的调用链上,反射2
检查权限时看到的是反射1的类,这就被欺骗了,导致安全漏洞。
使用CallerSensitive后,getCallerClass不再用固定深度去寻找
actual caller(“我”),而是把所有跟反射相关的接口方法都标注上
CallerSensitive,搜索时凡看到该注解都直接跳过,这样就有效解决了
前面举例的问题

Method.invoke()大佬总结图(个人搬运):

image-20200102232435118

-------------本文结束感谢您的阅读-------------