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、從哪里入手開始分析反射的實現(xiàn)原理?
例如現(xiàn)在有這么一個代碼案例:

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);

根據(jù)方法名和參數(shù)類型獲取對應(yīng)的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
//能夠執(zhí)行到這里,也就說明了沒有從本類以及父類中獲取到目標(biāo)方法的Method對象
interfaceCandidates.removeLessSpecifics();
//那么就從父接口返回的數(shù)組里查找,如果有,則返回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對象數(shù)組。
數(shù)組里的這些root對象是不能被用戶感知的,它們的作用是:被用于通過
ReflectionFactory.copyMethod方法創(chuàng)建root對象的副本對象。

再來分析下這個方法的源碼:

private Method[] privateGetDeclaredMethods(boolean publicOnly) {
    checkInitted();
    Method[] res;
    //首先從緩存中拿數(shù)據(jù),獲取Method root,如果緩存不為空,則將緩存中滿足
    //條件的各個方法對應(yīng)的Method root全部返回,以數(shù)組的形式
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數(shù)據(jù)
    //向虛擬機請求獲取本類中滿足條件的所有方法的Method root
    //this表明是本類,getDeclaredMethods0(publicOnly)是個native方法,
    //publicOnly即為向虛擬機請求的方法的過濾條件,是不是只需要獲取public的
    res = Reflection.filterMethods(this, getDeclaredMethods0(publicOnly));
    //從VM獲取到數(shù)據(jù)后
    if (rd != null) {
        //將獲取到的數(shù)據(jù)添加到緩存
        if (publicOnly) {
            rd.declaredPublicMethods = res;
        } else {
            rd.declaredMethods = res;
        }
    }
    //返回獲取到的Method root
    return res;
}

因為類中的每個方法所對應(yīng)的Method對象最初都是向VM請求獲得的,所以向VM請求獲得的那些Method對象,就是類中方法所對應(yīng)的Method root,即它們是各方法所對應(yīng)的Method對象的根對象,后面最終展現(xiàn)給開發(fā)者看得到的對象其實是通過對這些root對象進行拷貝得到的副本Method對象。

上一步獲取到Method root對象數(shù)組后,將這個數(shù)組作為searchMethods方法的參數(shù),跟進searchMethods方法看下:
它的邏輯是遍歷上一步獲取到的Method數(shù)組,逐個比較它們的與目標(biāo)方法的名稱、參數(shù)類型以及返回值類型是否一致,如果存在一致的,則通過res引用指向這個Method root對象,這個Method root對象就是本類中這個方法所對應(yīng)的根對象(直接向VM請求獲得的)。
最后遍歷完成后,比較res是否為null,如果為null,說明要反射的這個方法不存在,直接返回null,否則說明這個方法對應(yīng)的Method root是存在的,由res引用指向著,然后調(diào)用getReflectionFactory().copyMethod(res)對這個方法所對應(yīng)的Method root對象進行拷貝,得到這個root對象的副本對象,然后返回的是root對象的副本對象。

我們再看下copyMethod方法的邏輯,看看是如何對root對象進行拷貝的:
我們可以看到在copyMethod方法里調(diào)用的是傳入的root對象的copy()方法。

繼續(xù)跟進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對象調(diào)用的,root對象是分支結(jié)構(gòu)的第一層,即它本身
    //就是根節(jié)點了,它上面沒有節(jié)點的了,所以它的root屬性必定是null
    //如果不是null,說明這個對象不是root根對象,就不允許作為被拷貝的對象
    if (this.root != null)
        throw new IllegalArgumentException("Can not copy a non-root Method");

    //因為root對象已經(jīng)是一個存在的對象,現(xiàn)在是直接使用它的這些屬性值來創(chuàng)建
    //另一個對象,雖然對象地址不一樣了,但是對象的屬性值是一樣的,所以我們說
    //res是root對象的副本對象。
    Method res = new Method(clazz, name, parameterTypes, returnType,
                            exceptionTypes, modifiers, slot, signature,
                            annotations, parameterAnnotations, annotationDefault);
    //創(chuàng)建了副本對象之后,因為它是由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對象的副本就已經(jīng)創(chuàng)建好了,返回
    return res;
}

查看Method類的成員屬性的話,我們可以看到有一個Method root屬性。它的作用是
指向它的根節(jié)點,記錄本Method對象是由哪個root根節(jié)點對象拷貝的。

關(guān)于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分支結(jié)構(gòu)的層次目前只有兩層,例如第一層是Method root,第二層則是從root拷貝出來的其他的Method對象,并且它們的root引用都指向第一層的root對象。

如果第一層的root對象與第二層的Method對象之間存在環(huán)形引用的話,可能會發(fā)生死鎖。

———————————————————————————————————————————
獲取Method對象的流程分析完了之后我們獲得了一個Method方法,然后通過調(diào)用它的invoke方法實現(xiàn)目標(biāo)方法的調(diào)用,所以我們接著分析invoke方法的調(diào)用過程。
進入invoke方法:
先進行相關(guān)的權(quán)限檢查。
然后判斷ma是否為空,為空就創(chuàng)建一個MethodAccessor對象。

其中methodAccessor屬性使用了volatile關(guān)鍵字修飾,保證了它對所有線程的可見性。
private volatile MethodAccessor methodAccessor;

MethodAccessor是一個接口

image.png

MethodAccessor的實現(xiàn)類有3個

image.png

然后MethodAccessorImpl又是DelegatingMethodAccessorImpl和NativeMethodAccessorImpl的父類

這是創(chuàng)建MethodAccessor對象的邏輯,當(dāng)前對象的root屬性肯定不為null,因為當(dāng)前對象是一個root對象的副本對象,在前面的創(chuàng)建副本對象流程中我們知道,創(chuàng)建完副本對象后會將副本對象的root屬性指向root根節(jié)點Method對象,所以當(dāng)前對象的root屬性不為null,所以獲取root中的MethodAccessor對象,但是root根節(jié)點對象的MethodAccessor一開始是null的,所以獲取到的是null值,所以tmp!=null的邏輯就不通過了,所以就會調(diào)用newMethodAccessor方法創(chuàng)建MethodAccessor對象

可以看到newMethodAccessor方法剛開始有個checkInitted()邏輯

我們先看下checkInitted()的邏輯:

可以看到有獲取兩個系統(tǒng)屬性的邏輯:

System.getProperty("sun.reflect.noInflation");

System.getProperty("sun.reflect.inflationThreshold");

sun.reflect.noInflation這個參數(shù)的作用是決定是否啟用noinflation機制,ReflectionFactory.noInflation的默認(rèn)值是false,即默認(rèn)是有膨脹機制的。

所謂膨脹機制是指:一開始先使用native版本的MethodAccessor對象,等到native版本的調(diào)用次數(shù)達(dá)到sun.reflect.inflationThreshold所設(shè)定的閾值后,就會動態(tài)生成java版本的MethodAccessor對象來調(diào)用了。

而這個次數(shù)閾值就是通過sun.reflect.inflationThreshold這個系統(tǒng)參數(shù)設(shè)定的。

所以checkInitted()方法主要是獲取用戶設(shè)定的系統(tǒng)參數(shù)的值:sun.reflect.noInflation和sun.reflect.inflationThreshold。

如果用戶沒有設(shè)定,則使用默認(rèn)值:

ReflectionFactory.noInflation=false

ReflectionFactory.inflationThreshold=15

了解了newMethodAccessor方法的checkInitted()的邏輯后,我們再看下newMethodAccessor方法后面的邏輯:

創(chuàng)建NativeMethodAccessorImpl對象,然后把它作為參數(shù)傳給DelegatingMethodAccessorImpl的構(gòu)造方法,創(chuàng)建DelegatingMethodAccessorImpl對象

根據(jù)DelegatingMethodAccessorImpl的構(gòu)造方法邏輯:

this.setDelegate(var1);

從這里我們知道了,DelegatingMethodAccessorImpl里邊還有一個MethodAccessorImpl對象引用,而且它的invoke方法邏輯是調(diào)用的MethodAccessorImpl對象引用所指向的對象的invoke方法。

根據(jù)這個線路:通過一個對象來調(diào)用另一個對象,且這兩個對象中都定義了相同的方法,這不就是前面學(xué)過的典型的代理模式嗎。

所以我們知道了DelegatingMethodAccessorImpl的身份是作為一個代理類的,且由于代理類的邏輯已經(jīng)在源碼中實現(xiàn)了,所以這是一個靜態(tài)代理的典型應(yīng)用。

根據(jù)前面創(chuàng)建DelegatingMethodAccessorImpl代理對象時的傳參,我們知道它是將NativeMethodAccessorImpl對象傳給了DelegatingMethodAccessorImpl,并且把它賦予了MethodAccessorImpl對象引用,也就是說默認(rèn)情況下DelegatingMethodAccessorImpl一開始代理的是NativeMethodAccessorImpl類的對象。

而NativeMethodAccessorImpl中有一個DelegatingMethodAccessorImpl parent屬性,用于指向它被誰代理的。

因為在DelegatingMethodAccessorImpl類中有MethodAccessorImpl delegate屬性,它表示被代理的對象,那么DelegatingMethodAccessorImpl與NativeMethodAccessorImpl的關(guān)系可以描述為:
DelegatingMethodAccessorImpl代理NativeMethodAccessorImpl,DelegatingMethodAccessorImpl里存儲著它所代理的對象;
NativeMethodAccessorImpl被DelegatingMethodAccessorImpl代理,那么它里面存儲著它所對應(yīng)的代理對象。
numInvocations用于記錄調(diào)用nativeMethodAccessorImpl對象的調(diào)用次數(shù),它會與inflationThreshold進行比較,inflationThreshold的默認(rèn)值是15。
剛開始調(diào)用的代理對象調(diào)用的是NativeMethodAccessorImpl類對象,當(dāng)它的調(diào)用次數(shù)達(dá)到
inflationThreshold設(shè)定的閾值時就會動態(tài)創(chuàng)建一個java版本的目標(biāo)對象。
然后NativeMethodAccessorImpl類對象通過它里邊存儲的代理對象引用parent找到了代理對象,并調(diào)用代理對象的setDelegate(var3)方法更改被它代理的對象引用:

this.parent.setDelegate(var3);

也就是說一開始調(diào)用的是nativeMethodAccessorImpl對象,根據(jù)判斷邏輯是當(dāng)調(diào)用次數(shù)達(dá)到16次(閾值默認(rèn)是15次,超過15次,即在第16次就會動態(tài)創(chuàng)建java版本的目標(biāo)對象)時就會動態(tài)創(chuàng)建java版本的目標(biāo)對象,然后將java版本的對象作為新的被DelegatingMethodAccessorImpl代理的對象。

在第16次之后,即從第17次開始就不會再調(diào)用nativeMethodAccessorImpl對象的invoke方法了,而是調(diào)用的java版本的目標(biāo)對象的invoke方法。

如上所說,實際的MethodAccessor實現(xiàn)有兩個版本:
一個是Java實現(xiàn)的,另一個是native code實現(xiàn)的。

Java實現(xiàn)的版本在初始化時需要較多時間,但長久來說性能較好;
native版本正好相反,啟動時相對較快,但運行時間長了之后速度就比不過Java版了。

這是HotSpot的優(yōu)化方式帶來的性能特性,同時也是許多虛擬機的共同點:跨越native邊界會對優(yōu)化有阻礙作用,它就像個黑箱一樣讓虛擬機難以分析也將其內(nèi)聯(lián),于是運行時間長了之后反而是托管版本的代碼更快些。

為了權(quán)衡兩個版本的性能,Sun的JDK使用了“inflation”的技巧:讓Java方法在被反射調(diào)用時,開頭若干次使用native版,等反射調(diào)用次數(shù)超過閾值時則生成一個專用的MethodAccessor實現(xiàn)類,生成其中的invoke()方法的字節(jié)碼,以后對該Java方法的反射調(diào)用就會使用Java版。

可以在java啟動命令里加上-Dsun.reflect.noInflation=true,就可以把RefactionFactory的noInflation屬性設(shè)置為true了,這樣不用等到15次調(diào)用后,程序一開始就會用java版的MethodAccessor了。這個分析在上面已經(jīng)說過。
Sun的JDK是從1.4系開始采用這種優(yōu)化的。

創(chuàng)建了MethodAccessor對象后,newMethodAccessor方法返回

相當(dāng)于1步驟執(zhí)行完了,獲得了MethodAccessor對象,然后接著執(zhí)行setMethodAccessor(tmp)方法


把剛才創(chuàng)建的MethodAccessor對象賦予當(dāng)前Method對象的methodAccessor引用,并且判斷當(dāng)前Method對象的root屬性是否為null,肯定不為null的,因為它指向了root根節(jié)點Method對象,所以也會把剛才創(chuàng)建的MethodAccessor對象賦予root根節(jié)點對象的methodAccessor屬性。

然后我們的思路再回到Method對象的invoke方法


獲取到了MethodAccessor對象對象后,通過MethodAccessor對象來調(diào)用invoke方法,由于methodAccessor引用指向的是代理類DelegatingMethodAccessorImpl對象,所以實際上是調(diào)用DelegatingMethodAccessorImpl類的invoke方法,又由于DelegatingMethodAccessorImpl是代理類,實際上DelegatingMethodAccessorImpl的invoke方法調(diào)用的是被它代理的對象(native版本的或者java版本的)的invoke方法,最終才完成目標(biāo)方法的調(diào)用。

當(dāng)?shù)谝淮蝿?chuàng)建了類的某個方法的Method對象副本后,后面想要再創(chuàng)建這個方法的Method對象以及調(diào)用invoke方法的流程就比較簡單了:

因為第一次創(chuàng)建后,后面再創(chuàng)建的話,root方法會有緩存的,不需要再重新向VM請求了,直接從緩存拿到root對象。


如果要反射的方法確實存在,則res肯定非null,則對root對象進行拷貝


又會基于root根節(jié)點對象創(chuàng)建一個新的Method對象副本,且副本的root屬性還是指向root根節(jié)點對象,由于前面已經(jīng)創(chuàng)建過了副本,且創(chuàng)建了MethodAccessor對象,并且把MethodAccessor對象賦予了root根節(jié)點對象的methodAccessor引用,所以這里root根節(jié)點對象的methodAccessor引用不為null了,所以副本對象的methodAccessor引用直接初始化好了,全部指向第一次創(chuàng)建的MethodAccessor對象。

因為新的副本對象的methodAccessor屬性在前面創(chuàng)建的過程中已經(jīng)被初始化了,指向了第一次創(chuàng)建的那個MethodAccessor對象,所以這里的methodAccessor屬性是非null的了,所以不會再進入if語句塊重新創(chuàng)建MethodAccessor對象,直接調(diào)用MethodAccessor對象的invoke方法。

疑點解答:

為什么是第16次就會創(chuàng)建java版本的對象呢?

根據(jù)這個判斷邏輯,++this.numInvocations是先自增再與inflationThreshold閾值比較,所以結(jié)果是在第16次時,numInvocations的值才會大于inflationThreshold,才會執(zhí)行if語句塊。

If語句塊執(zhí)行結(jié)束后,還會最后一次nativeMethodAccessorImpl的invoke0方法。

所以,在所有的代理過程中,第16次所消耗的時間應(yīng)該是最長的,因為它包括了動態(tài)生成java對象的過程以及調(diào)用nativeMethodAccessorImpl對象的invoke0方法的過程。

而在這之前和之后的代理中要么只調(diào)用nativeMethodAccessorImpl的invoke0方法,要么調(diào)用java版本對象的invoke方法。

為什么要全部副本對象以及root對象的methodAccessor引用都指向同一個MethodAccessor對象呢?或者說為什么所有返回給用戶可見的Method對象都必須是root對象的副本對象呢?
我們上面分析說到,默認(rèn)情況下,代理對象會先調(diào)用native版本的methodAccessor對象,當(dāng)調(diào)用次數(shù)達(dá)到閾值后才會使用java版本的MethodAccessor對象。
如果我們每次創(chuàng)建的對象的methodAccessor屬性不是指向第一次創(chuàng)建的MethodAccessor對象,那么每個新創(chuàng)建的副本對象都得重新創(chuàng)建屬于自己的MethodAccessor對象對象,那么(在默認(rèn)情況下)每個副本對象又得經(jīng)歷一遍【先調(diào)用native MethodAccessor對象,調(diào)用次數(shù)達(dá)到閾值后再改為調(diào)用java版本的MethodAccessor對象】,這樣每個副本對象都得在native code和java code之間切換,性能損耗肯定更大,倒不如所有的副本對象都直接沿用之前已經(jīng)創(chuàng)建好的MethodAccessor對象,這樣就不需要每個副本對象都在native代碼和java代碼之間切換了,而是只需要當(dāng)這唯一的一個MethodAccessor對象的native版本的調(diào)用次數(shù)達(dá)到閾值后切換為java版本的就行了,由于只有唯一的一個MethodAccessor對象,所以對于同一個類的同一個方法來說,最多只需要切換一次。

因為每次創(chuàng)建同一個方法的Method對象,既然是同一個方法的,那么這個對象的所有內(nèi)容肯定應(yīng)該是相同的,所以所有的Method對象都需要從它的root對象拷貝而得這個也是合理的設(shè)計,再者,通過設(shè)定root節(jié)點對象,當(dāng)?shù)谝淮蝿?chuàng)建了MethodAccessor對象后,就可以將其保存到root對象中,后續(xù)再創(chuàng)建新的對象副本時,就不需要重新創(chuàng)建MethodAccessor對象了,而是直接從root對象獲取就行了,其目的也是上述所說的為了避免每個副本對象都需要經(jīng)歷從native MethodAccessor對象到j(luò)ava MethodAccessor對象的切換。

再者,前面也提到過,Method類的methodAccessor屬性是被volatile屬性修飾的。這說明什么呢?說明methodAccessor屬性可能會被多線程共享,它的變化需要保證對其他線程可見,所以通過volatile修飾,通過這里也能間接反映出methodAccessor對象不會創(chuàng)建多份,而是只有一份,然后被所有創(chuàng)建了這個方法副本對象的線程共享。

java反射源碼分析參考:
http://www.itdecent.cn/p/3ea4a6b57f87

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容