Java是一门静态语言,但是Java的反射赋予了Java动态的能力。Java的反射(reflection)机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法。这种动态获取程序信息以及动态调用对象的功能称为Java语言的反射机制。反射被视为动态语言的关键。反射功能可以让我们在运行时判断任意一个对象所属的类;在运行时构造任意一个类的对象;在运行时判断任意一个类所具有的成员变量和方法;在运行时调用任意一个对象的方法;生成动态代理。
一.Class
在HotSpot内部通过Klass模型来表示Java类的方法字段访问权限父类子类等信息。这为Java的反射提供了HotSpot内部表征信息,通过将Kclass对称到语言Java语言层面的Class类,我们就获得了反射能力。通过Object的getClass()方法,这种能力被每个类拥有。
java/lang/Object.java
public class Object {
@HotSpotIntrinsicCandidate
public final native Class<?> getClass();
}
jdk/src/java.base/share/native/libjava/Object.c
NIEXPORT jclass JNICALL
Java_java_lang_Object_getClass(JNIEnv *env, jobject this)
{
if (this == NULL) {
JNU_ThrowNullPointerException(env, NULL);
return 0;
} else {
return (*env)->GetObjectClass(env, this);
}
}
hotspot/src/share/vm/prims/jni.cpp
JNI_ENTRY(jclass, jni_GetObjectClass(JNIEnv *env, jobject obj))
JNIWrapper("GetObjectClass");
HOTSPOT_JNI_GETOBJECTCLASS_ENTRY(env, obj);
Klass* k = JNIHandles::resolve_non_null(obj)->klass();
jclass ret =
(jclass) JNIHandles::make_local(env, k->java_mirror());
HOTSPOT_JNI_GETOBJECTCLASS_RETURN(ret);
return ret;
JNI_END
此外通过Class.forName方法也可以返回运行时类,通过forName0和调用类的类加载器加载运行时类型信息
java/lang/Class.java
public final class Class<T> implements java.io.Serializable,
GenericDeclaration,
Type,
AnnotatedElement {
@CallerSensitive
public static Class<?> forName(String className)
throws ClassNotFoundException {
Class<?> caller = Reflection.getCallerClass();
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}
}
从当前线程的栈帧中找到正在执行的Java方法,由此方法来确定调用类
jdk/src/java.base/share/native/libjava/Reflection.c
JNIEXPORT jclass JNICALL
Java_jdk_internal_reflect_Reflection_getCallerClass__(JNIEnv *env, jclass unused)
{
return JVM_GetCallerClass(env, JVM_CALLER_DEPTH);
}
在Class类中提供了查询运行时类信息的方法,包括查询运行时类的字段,方法,注解,接口,构造函数,泛型信息,父类,访问权限等。这些信息赋予了上层框架搭建更多的灵活性。诸如实现注解处理器,动态代理,IOC,动态加载等特性。
二.reflect
java/lang/reflect
在Java平台框架层的reflect包下提供了一组运行时类反射操作类,通过进一步抽象运行时的类型信息,提取出诸如字段,方法,构造函数,数组,类型参数,类型,泛型,注解,访问修饰符等Java语言要素,并使用Java类来表示这些信息,为上层应用提供强大的反射能力。
jdk/internal/reflect
internal.reflect包赋予反射更强的能力,使得访问类的常量池信息,方法字段的字节码编辑等。通过InvocationHandler,Proxy,ProxyGenerator和jdk.internal.reflect包为上层应用提供动态生成类的代理能力。
三.invoke
对于反射在refect包下的类方法特性已经够用了,那么在invoke包下的反射特新则更进一步。refect包的动态是作用在虚拟机内部的,而invoke的动态能力则在字节码上,更加灵活,更像是在Java语法规则内手写字节码:自己创建方法签名(MethodType),自己决定调用方式(invokestatic/invokespecial/invokevirtual),自己注意访问控制(public/package/protected/private),最后还要自己决定类型隐式转换。invoke包配合reflect包为其他动态语言运行于虚拟机上提供了可能,如Scale,JPython,Kotlin等。
四.总结
- Reflection中的java.lang.reflect.Method对象远比MethodHandle机制信息含量多,换句话说,Reflection是重量级,而MethodHandle是轻量级
- Relection和MethodHandle机制都是在模拟方法调用,但Reflection是在模拟Java代码层次的方法调用,而MethodHandle是在模拟字节码层次的方法调用。在MethodHandles.lookup中的3个方法-findStaitc、findVirtual、findSpecial正是为了对应于invokestatic、invokevirtual和invokespecial这几条字节码指令的执行权限校验行为,而这些底层细节在使用Reflection API时是不需要关心的
- 由于MethodHandle是对字节码的方法指令调用的模拟,所以理论上虚拟机做了各种优化
- Reflection API的设计目标是只为Java语言服务的,而MethodHandle则设计成可服务于所有Java虚拟机之上的语言,其中也包括Java