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<T> 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 Method              root;

上面对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反射源码分析参考:
https://www.jianshu.com/p/3ea4a6b57f87

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,319评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,801评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,567评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,156评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,019评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,090评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,500评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,192评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,474评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,566评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,338评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,212评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,572评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,890评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,169评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,478评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,661评论 2 335

推荐阅读更多精彩内容