反射

概念

反射不同于正常new对象的方式,通过加载字节码的方式完成对类的加载,并对其做一些操作。

常用API

获取Class对象

Class clazz=Class.forName("com.xxx.Apple");

Class clazz=Apple.class;

Apple a=new Apple("???");
Class clazz=a.getClass();

获取类对象

Class clazz=Apple.class;

Constructor cons=clazz.getConstructor(String.class);
Apple a=cons.newInstance("大苹果");

获取方法、属性

Class clazz=Apple.class;

Field[] f=clazz.getFields();
Field[] f=class.getDeclaredFields();

源码解析

反射最精髓的莫过于invoke方法,进入 Method 的 invoke 方法我们可以看到,一开始是进行了一些权限的检查,最后是调用了 MethodAccessor 类的 invoke 方法进行进一步处理,如下图红色方框所示。

那么 MethodAccessor 又是什么呢?

其实 MethodAccessor 是一个接口,定义了方法调用的具体操作,而它有三个具体的实现类:

sun.reflect.DelegatingMethodAccessorImpl sun.reflect.MethodAccessorImpl sun.reflect.NativeMethodAccessorImpl 而要看 ma.invoke() 到底调用的是哪个类的 invoke 方法,则需要看看 MethodAccessor 对象返回的到底是哪个类对象,所以我们需要进入 acquireMethodAccessor() 方法中看看。

从 acquireMethodAccessor() 方法我们可以看到,代码先判断是否存在对应的 MethodAccessor 对象,如果存在那么就复用之前的 MethodAccessor 对象,否则调用 ReflectionFactory 对象的 newMethodAccessor 方法生成一个 MethodAccessor 对象。

在 ReflectionFactory 类的 newMethodAccessor 方法里,我们可以看到首先是生成了一个 NativeMethodAccessorImpl 对象,再这个对象作为参数调用 DelegatingMethodAccessorImpl 类的构造方法。

这里的实现是使用了代理模式,将 NativeMethodAccessorImpl 对象交给 DelegatingMethodAccessorImpl 对象代理。我们查看 DelegatingMethodAccessorImpl 类的构造方法可以知道,其实是将 NativeMethodAccessorImpl 对象赋值给 DelegatingMethodAccessorImpl 类的 delegate 属性。

所以说ReflectionFactory 类的 newMethodAccessor 方法最终返回 DelegatingMethodAccessorImpl 类对象。所以我们在前面的 ma.invoke() 里,其将会进入 DelegatingMethodAccessorImpl 类的 invoke 方法中。

进入 DelegatingMethodAccessorImpl 类的 invoke 方法后,这里调用了 delegate 属性的 invoke 方法,它又有两个实现类,分别是:DelegatingMethodAccessorImpl 和 NativeMethodAccessorImpl。按照我们前面说到的,这里的 delegate 其实是一个 NativeMethodAccessorImpl 对象,所以这里会进入 NativeMethodAccessorImpl 的 invoke 方法。

而在 NativeMethodAccessorImpl 的 invoke 方法里,其会判断调用次数是否超过阀值(numInvocations)。如果超过该阀值,那么就会生成另一个MethodAccessor 对象,并将原来 DelegatingMethodAccessorImpl 对象中的 delegate 属性指向最新的 MethodAccessor 对象。

到这里,其实我们可以知道 MethodAccessor 对象其实就是具体去生成反射类的入口。通过查看源码上的注释,我们可以了解到 MethodAccessor 对象的一些设计信息。

"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.Inflation 机制。初次加载字节码实现反射,使用 Method.invoke() 和 Constructor.newInstance() 加载花费的时间是使用原生代码加载花费时间的 3 - 4 倍。这使得那些频繁使用反射的应用需要花费更长的启动时间。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.为了避免这种痛苦的加载时间,我们在第一次加载的时候重用了 JVM 的入口,之后切换到字节码实现的实现。

就像注释里说的,实际的 MethodAccessor 实现有两个版本,一个是 Native 版本,一个是 Java 版本。 Native 版本一开始启动快,但是随着运行时间边长,速度变慢。Java 版本一开始加载慢,但是随着运行时间边长,速度变快。正是因为两种存在这些问题,所以第一次加载的时候我们会发现使用的是 NativeMethodAccessorImpl 的实现,而当反射调用次数超过 15 次之后,则使用 MethodAccessorGenerator 生成的 MethodAccessorImpl 对象去实现反射。 Method 类的 invoke 方法整个流程可以表示成如下的时序图:

讲到这里,我们了解了 Method 类的 invoke 方法的具体实现方式。知道了原来 invoke 方法内部有两种实现方式,一种是 native 原生的实现方式,一种是 Java 实现方式,这两种各有千秋。而为了最大化性能优势,JDK 源码使用了代理的设计模式去实现最大化性能。

转载自 http://www.cnblogs.com/chanshuyi/p/head_first_of_reflection.html#反射常用api

Last updated