2.45 反射原理
反射与RRTI
- 反射, 即提供运行时获取类信息, 调用方法等能力, 提供动态化功能; 可用于hook, 动态代理, 依赖注入等技术
- RRTI(Run-Time Type Identification)运行时类型识别, 作用是运行时识别一个对象的类型和类信息; 1). 传统的”RRTI”,它假定我们在编译期已知道了所有类型; 2). 反射机制,它允许我们在运行时发现和使用类型的信息
Class
对象的加载getClass()
, 会触发类的初始化阶段Class#forName()
, 会触发类的初始化阶段Class
字面常量, 即XXX.class
, 简单安全效率高, 但不会触发初始化ClassLoader#loadClass()
, 不会触发初始化
反射调用方法原理
- 首先Java通过字节码获取类的基本信息,其次通过Class#getMethod(“myMethod”)经过查找获取
Method
对象 - 接着通过
Method#invoke
调用方法, 此处包括Native版本和Java版本实现 - Native版本的
invoke0()
在HotSpot VM里是由JVM_InvokeMethod()
支持 JVM_InvokeMethod()
中通过JNIHandles::resolve(method)
解析方法的符号引用(个人理解, 详情参考R大博客)- 拿到
method_handle
后, 即符号引用, 获取方法的返回类型等相关信息, 最后调用invoke()
执行方法(个人理解, 详情参考R大博客)- 包含native版本以及Java版本, native版本初始化快, 但由于对JVM是黑盒, 因此无法内联等优化, 当超过15次时, 会自动转成Java版本, Java版本初始化慢, 但长时间执行效率更高, 可进行内联等优化
public final
class Method extends AccessibleObject implements GenericDeclaration, Member {
public Object invoke(Object obj, Object... args)
throws IllegalAccessException, IllegalArgumentException,
InvocationTargetException
{
...
return methodAccessor.invoke(obj, args);
}
}
// methodAccessor通过ReflectionFactory实例化返回
public class ReflectionFactory {
public MethodAccessor newMethodAccessor(Method method) {
...
if (noInflation) {
return new MethodAccessorGenerator().
generateMethod(method.getDeclaringClass(),
method.getName(),
method.getParameterTypes(),
method.getReturnType(),
method.getExceptionTypes(),
method.getModifiers());
} else {
NativeMethodAccessorImpl acc =
new NativeMethodAccessorImpl(method);
DelegatingMethodAccessorImpl res =
new DelegatingMethodAccessorImpl(acc);
acc.setParent(res);
return res;
}
}
}
// native版本则是直接调用Reflection::invoke_method()
class NativeMethodAccessorImpl extends MethodAccessorImpl {
...
public Object invoke(Object obj, Object[] args)
throws IllegalArgumentException, InvocationTargetException
{
if (++numInvocations > ReflectionFactory.inflationThreshold()) {
MethodAccessorImpl acc = (MethodAccessorImpl)
new MethodAccessorGenerator().
generateMethod(method.getDeclaringClass(),
method.getName(),
method.getParameterTypes(),
method.getReturnType(),
method.getExceptionTypes(),
method.getModifiers());
parent.setDelegate(acc);
}
return invoke0(method, obj, args);
}
...
private static native Object invoke0(Method m, Object obj, Object[] args);
}
// Java版本, 通过MethodAccessorGenerator生成MethodAccessor的实现类
public class GeneratedMethodAccessor1 extends MethodAccessorImpl {
...
public Object invoke(Object obj, Object[] args)
throws IllegalArgumentException, InvocationTargetException {
...
try {
target.foo(arg0);
} catch (Throwable t) {
throw new InvocationTargetException(t);
}
}
}
Field
- 使用反射获取或者修改一个变量的值时,编译器不会进行自动装/拆箱
public class FieldTrouble extends BaseTestClass {
public Integer value;
public static void main(String[] args) {
FieldTrouble fieldTrouble = new FieldTrouble();
Class<? extends FieldTrouble> cls = fieldTrouble.getClass();
...
Field value = cls.getField("value");
// 抛java.lang.IllegalArgumentException
value.setInt(fieldTrouble, 23);
...
}
}
-
访问限制阻止我们修改 final 类型的变量,
final
变量通过反射修改需要调用Filed#setAccessible(true)
-
在使用反射修改某个对象的成员变量前你要明白,这样做会造成一定程度的性能开销,因为在反射时这样的操作需要引发许多额外操作,比如验证访问权限等。另外使用反射也会导致一些运行时的计算优化失效
Method
synthetic method
合成方法
public class Foo {
private int get(){
return 1;
}
private class Bar {
private Bar() {
System.out.println(get());
}
}
}
...
// Synthetic (合成)方法是由编译器产生的、源代码中没有的方法。当内部类与外部类之前有互相访问 private 属性、方法时,编译器会在运行时为调用方创建一个 synthetic 方法。
static int access$000(Foo); //多出来的 synthetic 方法,为了在 Bar 中的这段代码 System.out.println(get());
varagrs
方法
public void testVarargs(String... args) {
...
}
bride
桥接方法, 为了兼容JDK 1.5版本以前的代码
反射慢的原因
- 运行时通过Class对象查找
Method
等对象 - 方法调用时, 编译器无法进行方法内联等优化
- 需要校验, 根据方法名和签名查找方法的符号引用等
反射优化
- 缓存, 避免多次加载
- 调用
method#setAccessible(true)
避免安全检查缓存 - 直接调用方法