java反射源码分析,思路超清晰
参考:https://www.cnblogs.com/chanshuyi/p/head_first_of_reflection.html#%E5%8F%8D%E5%B0%84%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90
1、从哪里入手开始分析反射的实现原理?
例如现在有这么一个代码案例:
Person类:
public class Person {public String role(String role){
System.out.println("role: " + role);
return role;
}public String name(String name){
System.out.println("name: " + name);
return name;
}
}
测试类:
public class Gener {public static void main(String[] args) {
try {
Class> clazz = Class.forName("test.Person");
Method name = clazz.getMethod("name", String.class);
Person person = (Person)clazz.newInstance();
String personName = (String)name.invoke(person, new String[]{"tony"});
System.out.println(personName);
} catch (Exception e) {
e.printStackTrace();
}
}
}
我们以方法的反射为例进行分析:
这行代码作用是获取Method对象:
Method name = clazz.getMethod("role", String.class);
根据方法名和参数类型获取对应的Method对象
文章图片
private Method getMethod0(String name, Class>[] parameterTypes, boolean includeStaticMethods) {
MethodArray interfaceCandidates = new MethodArray(2);
//获取方法的Method对象
Method res =privateGetMethodRecursive(name, parameterTypes, includeStaticMethods, interfaceCandidates);
//如果不为null,说明从本类或者父类中获取到了root对象的拷贝对象,直接返回
if (res != null)
return res;
// Not found on class or superclass directly
//能够执行到这里,也就说明了没有从本类以及父类中获取到目标方法的Method对象
interfaceCandidates.removeLessSpecifics();
//那么就从父接口返回的数组里查找,如果有,则返回Method对象,否则返回null
return interfaceCandidates.getFirst();
// may be null
}
getMethod0方法的逻辑:
文章图片
跟进privateGetMethodRecursive方法:
private Method privateGetMethodRecursive(String name,
Class>[] parameterTypes,
boolean includeStaticMethods,
MethodArray allInterfaceCandidates) {// Must _not_ return root methods
Method res;
// 先查找本类是否有这个方法
if ((res = searchMethods(privateGetDeclaredMethods(true),
name,
parameterTypes)) != null) {
if (includeStaticMethods || !Modifier.isStatic(res.getModifiers()))
return res;
}//如果子类没有的话,再从它的父类中找是不是有这个方法
if (!isInterface()) {
Class super T> c = getSuperclass();
if (c != null) {
if ((res = c.getMethod0(name, parameterTypes, true)) != null) {
return res;
}
}
}
//如果子类和父类中都找不到,那么再从父接口中查找
Class>[] interfaces = getInterfaces();
for (Class> c : interfaces)
if ((res = c.getMethod0(name, parameterTypes, false)) != null)
allInterfaceCandidates.add(res);
// 如果子类、父类和父接口中都找不到这个方法,那么就返回null
return null;
}
跟进privateGetDeclaredMethods方法看看:
我们先看下这个方法的注释,通过注释来了解方法的作用,注释翻译过来是:
这个方法返回一个Method的root对象数组。
数组里的这些root对象是不能被用户感知的,它们的作用是:被用于通过
ReflectionFactory.copyMethod方法创建root对象的副本对象。
文章图片
再来分析下这个方法的源码:
private Method[] privateGetDeclaredMethods(boolean publicOnly) {
checkInitted();
Method[] res;
//首先从缓存中拿数据,获取Method root,如果缓存不为空,则将缓存中满足
//条件的各个方法对应的Method root全部返回,以数组的形式
ReflectionData rd = reflectionData();
if (rd != null) {
res = publicOnly ? rd.declaredPublicMethods : rd.declaredMethods;
if (res != null) return res;
}
// No cached value available;
request value from VM
//如果缓存中没有满足条件的Method root数据
//向虚拟机请求获取本类中满足条件的所有方法的Method root
//this表明是本类,getDeclaredMethods0(publicOnly)是个native方法,
//publicOnly即为向虚拟机请求的方法的过滤条件,是不是只需要获取public的
res = Reflection.filterMethods(this, getDeclaredMethods0(publicOnly));
//从VM获取到数据后
if (rd != null) {
//将获取到的数据添加到缓存
if (publicOnly) {
rd.declaredPublicMethods = res;
} else {
rd.declaredMethods = res;
}
}
//返回获取到的Method root
return res;
}
因为类中的每个方法所对应的Method对象最初都是向VM请求获得的,所以向VM请求获得的那些Method对象,就是类中方法所对应的Method root,即它们是各方法所对应的Method对象的根对象,后面最终展现给开发者看得到的对象其实是通过对这些root对象进行拷贝得到的副本Method对象。
上一步获取到Method root对象数组后,将这个数组作为searchMethods方法的参数,跟进searchMethods方法看下:
它的逻辑是遍历上一步获取到的Method数组,逐个比较它们的与目标方法的名称、参数类型以及返回值类型是否一致,如果存在一致的,则通过res引用指向这个Method root对象,这个Method root对象就是本类中这个方法所对应的根对象(直接向VM请求获得的)。
最后遍历完成后,比较res是否为null,如果为null,说明要反射的这个方法不存在,直接返回null,否则说明这个方法对应的Method root是存在的,由res引用指向着,然后调用getReflectionFactory().copyMethod(res)对这个方法所对应的Method root对象进行拷贝,得到这个root对象的副本对象,然后返回的是root对象的副本对象。
文章图片
我们再看下copyMethod方法的逻辑,看看是如何对root对象进行拷贝的:
我们可以看到在copyMethod方法里调用的是传入的root对象的copy()方法。
文章图片
继续跟进copy()方法里看下:
文章图片
Method copy() {
// This routine enables sharing of MethodAccessor objects
// among Method objects which refer to the same underlying
// method in the VM. (All of this contortion is only necessary
// because of the "accessibility" bit in AccessibleObject,
// which implicitly requires that new java.lang.reflect
// objects be fabricated for each reflective call on Class
// objects.)
//由于这个方法是root对象调用的,root对象是分支结构的第一层,即它本身
//就是根节点了,它上面没有节点的了,所以它的root属性必定是null
//如果不是null,说明这个对象不是root根对象,就不允许作为被拷贝的对象
if (this.root != null)
throw new IllegalArgumentException("Can not copy a non-root Method");
//因为root对象已经是一个存在的对象,现在是直接使用它的这些属性值来创建
//另一个对象,虽然对象地址不一样了,但是对象的属性值是一样的,所以我们说
//res是root对象的副本对象。
Method res = new Method(clazz, name, parameterTypes, returnType,
exceptionTypes, modifiers, slot, signature,
annotations, parameterAnnotations, annotationDefault);
//创建了副本对象之后,因为它是由root对象拷贝而来的,所以将res的root引用
//指向this对象,即root对象。
res.root = this;
// Might as well eagerly propagate this if already present
//res的methodAccessor引用指向this,即root对象的methodAccessor对象。
res.methodAccessor = methodAccessor;
//然后root对象的副本就已经创建好了,返回
return res;
}
查看Method类的成员属性的话,我们可以看到有一个Method root属性。它的作用是
指向它的根节点,记录本Method对象是由哪个root根节点对象拷贝的。
关于Method类的root属性的含义我们可以看下官方注释:
// For sharing of MethodAccessors. This branching structure is
// currently only two levels deep (i.e., one root Method and
// potentially many Method objects pointing to it.)
//
// If this branching structure would ever contain cycles, deadlocks can
// occur in annotation code.private Methodroot;
上面对root属性的注释翻译过来就是:
在Method类中声明一个Method类型的root属性的目的是为了共享methodAccessor对象。Method分支结构的层次目前只有两层,例如第一层是Method root,第二层则是从root拷贝出来的其他的Method对象,并且它们的root引用都指向第一层的root对象。
如果第一层的root对象与第二层的Method对象之间存在环形引用的话,可能会发生死锁。
文章图片
———————————————————————————————————————————
获取Method对象的流程分析完了之后我们获得了一个Method方法,然后通过调用它的invoke方法实现目标方法的调用,所以我们接着分析invoke方法的调用过程。
进入invoke方法:
先进行相关的权限检查。
然后判断ma是否为空,为空就创建一个MethodAccessor对象。
其中methodAccessor属性使用了volatile关键字修饰,保证了它对所有线程的可见性。
private volatile MethodAccessor methodAccessor;
文章图片
MethodAccessor是一个接口
文章图片
image.png MethodAccessor的实现类有3个
文章图片
image.png 然后MethodAccessorImpl又是DelegatingMethodAccessorImpl和NativeMethodAccessorImpl的父类
文章图片
这是创建MethodAccessor对象的逻辑,当前对象的root属性肯定不为null,因为当前对象是一个root对象的副本对象,在前面的创建副本对象流程中我们知道,创建完副本对象后会将副本对象的root属性指向root根节点Method对象,所以当前对象的root属性不为null,所以获取root中的MethodAccessor对象,但是root根节点对象的MethodAccessor一开始是null的,所以获取到的是null值,所以tmp!=null的逻辑就不通过了,所以就会调用newMethodAccessor方法创建MethodAccessor对象
文章图片
可以看到newMethodAccessor方法刚开始有个checkInitted()逻辑
文章图片
我们先看下checkInitted()的逻辑:
可以看到有获取两个系统属性的逻辑:
System.getProperty("sun.reflect.noInflation");
System.getProperty("sun.reflect.inflationThreshold");
sun.reflect.noInflation这个参数的作用是决定是否启用noinflation机制,ReflectionFactory.noInflation的默认值是false,即默认是有膨胀机制的。
所谓膨胀机制是指:一开始先使用native版本的MethodAccessor对象,等到native版本的调用次数达到sun.reflect.inflationThreshold所设定的阈值后,就会动态生成java版本的MethodAccessor对象来调用了。
而这个次数阈值就是通过sun.reflect.inflationThreshold这个系统参数设定的。
所以checkInitted()方法主要是获取用户设定的系统参数的值:sun.reflect.noInflation和sun.reflect.inflationThreshold。
如果用户没有设定,则使用默认值:
ReflectionFactory.noInflation=false
ReflectionFactory.inflationThreshold=15
文章图片
了解了newMethodAccessor方法的checkInitted()的逻辑后,我们再看下newMethodAccessor方法后面的逻辑:
创建NativeMethodAccessorImpl对象,然后把它作为参数传给DelegatingMethodAccessorImpl的构造方法,创建DelegatingMethodAccessorImpl对象
文章图片
根据DelegatingMethodAccessorImpl的构造方法逻辑:
this.setDelegate(var1);
从这里我们知道了,DelegatingMethodAccessorImpl里边还有一个MethodAccessorImpl对象引用,而且它的invoke方法逻辑是调用的MethodAccessorImpl对象引用所指向的对象的invoke方法。
根据这个线路:通过一个对象来调用另一个对象,且这两个对象中都定义了相同的方法,这不就是前面学过的典型的代理模式吗。
所以我们知道了DelegatingMethodAccessorImpl的身份是作为一个代理类的,且由于代理类的逻辑已经在源码中实现了,所以这是一个静态代理的典型应用。
根据前面创建DelegatingMethodAccessorImpl代理对象时的传参,我们知道它是将NativeMethodAccessorImpl对象传给了DelegatingMethodAccessorImpl,并且把它赋予了MethodAccessorImpl对象引用,也就是说默认情况下DelegatingMethodAccessorImpl一开始代理的是NativeMethodAccessorImpl类的对象。
文章图片
而NativeMethodAccessorImpl中有一个DelegatingMethodAccessorImpl parent属性,用于指向它被谁代理的。
因为在DelegatingMethodAccessorImpl类中有MethodAccessorImpl delegate属性,它表示被代理的对象,那么DelegatingMethodAccessorImpl与NativeMethodAccessorImpl的关系可以描述为:
DelegatingMethodAccessorImpl代理NativeMethodAccessorImpl,DelegatingMethodAccessorImpl里存储着它所代理的对象;
NativeMethodAccessorImpl被DelegatingMethodAccessorImpl代理,那么它里面存储着它所对应的代理对象。
numInvocations用于记录调用nativeMethodAccessorImpl对象的调用次数,它会与inflationThreshold进行比较,inflationThreshold的默认值是15。
刚开始调用的代理对象调用的是NativeMethodAccessorImpl类对象,当它的调用次数达到
inflationThreshold设定的阈值时就会动态创建一个java版本的目标对象。
然后NativeMethodAccessorImpl类对象通过它里边存储的代理对象引用parent找到了代理对象,并调用代理对象的setDelegate(var3)方法更改被它代理的对象引用:
this.parent.setDelegate(var3);
也就是说一开始调用的是nativeMethodAccessorImpl对象,根据判断逻辑是当调用次数达到16次(阈值默认是15次,超过15次,即在第16次就会动态创建java版本的目标对象)时就会动态创建java版本的目标对象,然后将java版本的对象作为新的被DelegatingMethodAccessorImpl代理的对象。
在第16次之后,即从第17次开始就不会再调用nativeMethodAccessorImpl对象的invoke方法了,而是调用的java版本的目标对象的invoke方法。
文章图片
如上所说,实际的MethodAccessor实现有两个版本:
一个是Java实现的,另一个是native code实现的。
Java实现的版本在初始化时需要较多时间,但长久来说性能较好;
native版本正好相反,启动时相对较快,但运行时间长了之后速度就比不过Java版了。
这是HotSpot的优化方式带来的性能特性,同时也是许多虚拟机的共同点:跨越native边界会对优化有阻碍作用,它就像个黑箱一样让虚拟机难以分析也将其内联,于是运行时间长了之后反而是托管版本的代码更快些。
为了权衡两个版本的性能,Sun的JDK使用了“inflation”的技巧:让Java方法在被反射调用时,开头若干次使用native版,等反射调用次数超过阈值时则生成一个专用的MethodAccessor实现类,生成其中的invoke()方法的字节码,以后对该Java方法的反射调用就会使用Java版。
可以在java启动命令里加上-Dsun.reflect.noInflation=true,就可以把RefactionFactory的noInflation属性设置为true了,这样不用等到15次调用后,程序一开始就会用java版的MethodAccessor了。这个分析在上面已经说过。
Sun的JDK是从1.4系开始采用这种优化的。
创建了MethodAccessor对象后,newMethodAccessor方法返回
文章图片
相当于1步骤执行完了,获得了MethodAccessor对象,然后接着执行setMethodAccessor(tmp)方法
文章图片
把刚才创建的MethodAccessor对象赋予当前Method对象的methodAccessor引用,并且判断当前Method对象的root属性是否为null,肯定不为null的,因为它指向了root根节点Method对象,所以也会把刚才创建的MethodAccessor对象赋予root根节点对象的methodAccessor属性。
然后我们的思路再回到Method对象的invoke方法
文章图片
获取到了MethodAccessor对象对象后,通过MethodAccessor对象来调用invoke方法,由于methodAccessor引用指向的是代理类DelegatingMethodAccessorImpl对象,所以实际上是调用DelegatingMethodAccessorImpl类的invoke方法,又由于DelegatingMethodAccessorImpl是代理类,实际上DelegatingMethodAccessorImpl的invoke方法调用的是被它代理的对象(native版本的或者java版本的)的invoke方法,最终才完成目标方法的调用。
当第一次创建了类的某个方法的Method对象副本后,后面想要再创建这个方法的Method对象以及调用invoke方法的流程就比较简单了:
因为第一次创建后,后面再创建的话,root方法会有缓存的,不需要再重新向VM请求了,直接从缓存拿到root对象。
文章图片
如果要反射的方法确实存在,则res肯定非null,则对root对象进行拷贝
文章图片
又会基于root根节点对象创建一个新的Method对象副本,且副本的root属性还是指向root根节点对象,由于前面已经创建过了副本,且创建了MethodAccessor对象,并且把MethodAccessor对象赋予了root根节点对象的methodAccessor引用,所以这里root根节点对象的methodAccessor引用不为null了,所以副本对象的methodAccessor引用直接初始化好了,全部指向第一次创建的MethodAccessor对象。
文章图片
因为新的副本对象的methodAccessor属性在前面创建的过程中已经被初始化了,指向了第一次创建的那个MethodAccessor对象,所以这里的methodAccessor属性是非null的了,所以不会再进入if语句块重新创建MethodAccessor对象,直接调用MethodAccessor对象的invoke方法。
文章图片
疑点解答:
为什么是第16次就会创建java版本的对象呢?
文章图片
根据这个判断逻辑,++this.numInvocations是先自增再与inflationThreshold阈值比较,所以结果是在第16次时,numInvocations的值才会大于inflationThreshold,才会执行if语句块。
If语句块执行结束后,还会最后一次nativeMethodAccessorImpl的invoke0方法。
所以,在所有的代理过程中,第16次所消耗的时间应该是最长的,因为它包括了动态生成java对象的过程以及调用nativeMethodAccessorImpl对象的invoke0方法的过程。
而在这之前和之后的代理中要么只调用nativeMethodAccessorImpl的invoke0方法,要么调用java版本对象的invoke方法。
为什么要全部副本对象以及root对象的methodAccessor引用都指向同一个MethodAccessor对象呢?或者说为什么所有返回给用户可见的Method对象都必须是root对象的副本对象呢?
我们上面分析说到,默认情况下,代理对象会先调用native版本的methodAccessor对象,当调用次数达到阈值后才会使用java版本的MethodAccessor对象。
如果我们每次创建的对象的methodAccessor属性不是指向第一次创建的MethodAccessor对象,那么每个新创建的副本对象都得重新创建属于自己的MethodAccessor对象对象,那么(在默认情况下)每个副本对象又得经历一遍【先调用native MethodAccessor对象,调用次数达到阈值后再改为调用java版本的MethodAccessor对象】,这样每个副本对象都得在native code和java code之间切换,性能损耗肯定更大,倒不如所有的副本对象都直接沿用之前已经创建好的MethodAccessor对象,这样就不需要每个副本对象都在native代码和java代码之间切换了,而是只需要当这唯一的一个MethodAccessor对象的native版本的调用次数达到阈值后切换为java版本的就行了,由于只有唯一的一个MethodAccessor对象,所以对于同一个类的同一个方法来说,最多只需要切换一次。
因为每次创建同一个方法的Method对象,既然是同一个方法的,那么这个对象的所有内容肯定应该是相同的,所以所有的Method对象都需要从它的root对象拷贝而得这个也是合理的设计,再者,通过设定root节点对象,当第一次创建了MethodAccessor对象后,就可以将其保存到root对象中,后续再创建新的对象副本时,就不需要重新创建MethodAccessor对象了,而是直接从root对象获取就行了,其目的也是上述所说的为了避免每个副本对象都需要经历从native MethodAccessor对象到java MethodAccessor对象的切换。
再者,前面也提到过,Method类的methodAccessor属性是被volatile属性修饰的。这说明什么呢?说明methodAccessor属性可能会被多线程共享,它的变化需要保证对其他线程可见,所以通过volatile修饰,通过这里也能间接反映出methodAccessor对象不会创建多份,而是只有一份,然后被所有创建了这个方法副本对象的线程共享。
文章图片
【java反射源码分析,思路超清晰】java反射源码分析参考:
https://www.jianshu.com/p/3ea4a6b57f87
推荐阅读
- JAVA(抽象类与接口的区别&重载与重写&内存泄漏)
- 事件代理
- Java|Java OpenCV图像处理之SIFT角点检测详解
- java中如何实现重建二叉树
- 数组常用方法一
- 【Hadoop踩雷】Mac下安装Hadoop3以及Java版本问题
- Android事件传递源码分析
- Java|Java基础——数组
- RxJava|RxJava 在Android项目中的使用(一)
- java之static、static|java之static、static final、final的区别与应用