2.45 反射原理

1 minute read

反射与RRTI

  • 反射, 即提供运行时获取类信息, 调用方法等能力, 提供动态化功能; 可用于hook, 动态代理, 依赖注入等技术
  • RRTI(Run-Time Type Identification)运行时类型识别, 作用是运行时识别一个对象的类型和类信息; 1). 传统的”RRTI”,它假定我们在编译期已知道了所有类型; 2). 反射机制,它允许我们在运行时发现和使用类型的信息
  • Class对象的加载
    • getClass(), 会触发类的初始化阶段
    • Class#forName(), 会触发类的初始化阶段
    • Class字面常量, 即XXX.class, 简单安全效率高, 但不会触发初始化
    • ClassLoader#loadClass(), 不会触发初始化

反射调用方法原理

  1. 首先Java通过字节码获取类的基本信息,其次通过Class#getMethod(“myMethod”)经过查找获取Method对象
  2. 接着通过Method#invoke调用方法, 此处包括Native版本和Java版本实现
  3. Native版本的invoke0()在HotSpot VM里是由JVM_InvokeMethod()支持
  4. JVM_InvokeMethod()中通过JNIHandles::resolve(method)解析方法的符号引用(个人理解, 详情参考R大博客)
  5. 拿到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版本以前的代码

反射慢的原因

  1. 运行时通过Class对象查找Method等对象
  2. 方法调用时, 编译器无法进行方法内联等优化
  3. 需要校验, 根据方法名和签名查找方法的符号引用等

反射优化

  • 缓存, 避免多次加载
  • 调用method#setAccessible(true)避免安全检查缓存
  • 直接调用方法

参考