超車時刻:Java反射源碼解析

在《一篇文章全面瞭解Java反射機制》中我們學習了Java反射的基本使用,這篇文章就帶大家一起來看看核心源碼。這可是與新手拉開差距的機會。

關於反射的類

關於反射的類是很多的,我們在基礎篇中已經涉及到一部分比如:Filed、Method、Constructor。同時,還有一些我們沒有看到的類,比如:AccessibleObject、ReflectionFactory、MethodAccessor等。

本篇文章我們重點介紹Method類的invoke方法的處理邏輯,這也是Java反射最核心的部分。

常見反射異常

我們在使用一些框架時經常會看到類似如下的異常:

at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)

這類異常便是通過反射機制實現的方法,在執行Method的invoke方法時拋出的異常。

比如,在Spring的xml配置文件中配置了不存在的類時,異常堆棧便會將異常指向調用的invoke方法。

所以,當你遇到類似的異常,可以簡單推斷一下,你所使用的框架可能使用了反射機制。

下面,我們就來看看Method的invoke方法到底做了些什麼。

源碼分析

直接點擊程序中調用的invoke方法,查看第一層源代碼:

@CallerSensitive
public Object invoke(Object obj, Object... args)
    throws IllegalAccessException, IllegalArgumentException,
       InvocationTargetException{
    if (!override) {
        if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
            Class<?> caller = Reflection.getCallerClass();
            checkAccess(caller, clazz, obj, modifiers);
        }
    }
    MethodAccessor ma = methodAccessor;             // read volatile
    if (ma == null) {
        ma = acquireMethodAccessor();
    }
    return ma.invoke(obj, args);
}

@CallerSensitive註解:這個註解是Java修復漏洞用的。防止使用者使用雙重反射來提升權限,原理是因爲當時反射只檢查深度的調用者的類是否有權限,本身的類是沒有這麼高權限的,但是可以通過多重反射來提高調用的權限。

使用該註解,getCallerClass方法就會直接跳過有 @CallerSensitive修飾的接口方法,直接查找真實的調用者(actual caller)。

在invoke方法的前半部部分主要是用來做一些檢查工作,重點在於ma.invoke(obj, args)方法。這裏使用到了MethodAccessor接口,該接口位於sun.reflect包下,是生成反射類的入口,此部分屬於未開源部分。

在MethodAccessor中定義了invoke方法:

public interface MethodAccessor {
    Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException;
}

該接口默認有三個實現類:

sun.reflect.DelegatingMethodAccessorImpl
sun.reflect.MethodAccessorImpl
sun.reflect.NativeMethodAccessorImpl

默認情況下methodAccessor值是爲null的,那麼看看acquireMethodAccessor方法是如何創建MethodAccessor的實現類的。

private MethodAccessor acquireMethodAccessor() {
    // First check to see if one has been created yet, and take it
    // if so
    MethodAccessor tmp = null;
    if (root != null) tmp = root.getMethodAccessor();
    if (tmp != null) {
        methodAccessor = tmp;
    } else {
        // Otherwise fabricate one and propagate it up to the root
        tmp = reflectionFactory.newMethodAccessor(this);
        setMethodAccessor(tmp);
    }

    return tmp;
}

acquireMethodAccessor方法中首先判斷是否存在MethodAccessor的實例,如果存在則直接拿來使用。否則,調用ReflectionFactory的newMethodAccessor方法來創建一個,創建完成並設置到root配置中。

繼續看newMethodAccessor的創建過程:

public MethodAccessor newMethodAccessor(Method var1) {
    checkInitted();
    if (noInflation && !ReflectUtil.isVMAnonymousClass(var1.getDeclaringClass())) {
        return (new MethodAccessorGenerator()).generateMethod(var1.getDeclaringClass(), var1.getName(), var1.getParameterTypes(), var1.getReturnType(), var1.getExceptionTypes(), var1.getModifiers());
    } else {
        NativeMethodAccessorImpl var2 = new NativeMethodAccessorImpl(var1);
        DelegatingMethodAccessorImpl var3 = new DelegatingMethodAccessorImpl(var2);
        var2.setParent(var3);
        return var3;
    }
}

通過debug會發現,默認情況下首先進入else處理邏輯中。在else中創建了一個NativeMethodAccessorImpl對象,並作爲構造參數傳入了DelegatingMethodAccessorImpl的構造方法中。

這裏很明顯使用了代理模式(可參看《Java代理模式及動態代理詳解》一文),將NativeMethodAccessorImpl對象交給 DelegatingMethodAccessorImpl對象代理。同時,通過setParent方法,NativeMethodAccessorImpl也持有了DelegatingMethodAccessorImpl的引用。

看你一下DelegatingMethodAccessorImpl的源碼,你會發現它就是代理模式的標準實現:

class DelegatingMethodAccessorImpl extends MethodAccessorImpl {
    private MethodAccessorImpl delegate;

    DelegatingMethodAccessorImpl(MethodAccessorImpl var1) {
        this.setDelegate(var1);
    }

    public Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException {
        return this.delegate.invoke(var1, var2);
    }

    void setDelegate(MethodAccessorImpl var1) {
        this.delegate = var1;
    }
}

NativeMethodAccessorImpl被賦值給DelegatingMethodAccessorImpl中的DelegatingMethodAccessorImpl屬性,同時這兩個類都實現了MethodAccessorImpl接口。而在DelegatingMethodAccessorImpl又包裝了invoke方法。靜態代理的標準實現方式。

經過代碼跟蹤,我們發現ReflectionFactory類的newMethodAccessor方法返回的是DelegatingMethodAccessorImpl類對象。那麼ma.invoke()方法調用的是DelegatingMethodAccessorImpl的invoke方法。

而DelegatingMethodAccessorImpl又調用了設置的NativeMethodAccessorImpl對象的invoke方法。

public Object invoke(Object var1, Object[] var2) throws IllegalArgumentException, InvocationTargetException {
    if (++this.numInvocations > ReflectionFactory.inflationThreshold() && !ReflectUtil.isVMAnonymousClass(this.method.getDeclaringClass())) {
        MethodAccessorImpl var3 = (MethodAccessorImpl)(new MethodAccessorGenerator()).generateMethod(this.method.getDeclaringClass(), this.method.getName(), this.method.getParameterTypes(), this.method.getReturnType(), this.method.getExceptionTypes(), this.method.getModifiers());
        this.parent.setDelegate(var3);
    }

    return invoke0(this.method, var1, var2);
}

該invoke方法中首先會判斷numInvocations是否會大於一個閾值,改值默認爲:

private static int inflationThreshold = 15;

如果大於該值並且不是匿名類則會進行新的MethodAccessorImpl的創建,並且賦值給代理類DelegatingMethodAccessorImpl。也就是說創建了一個新的實現類把上面原有的實現類給替換掉了。

在MethodAccessor的具體實現中使用了Inflation(通貨膨脹)機制。初次加載字節碼實現反射,使用Method.invoke()和Constructor.newInstance()加載花費的時間是使用原生代碼加載花費時間的3到4倍。這使得那些頻繁使用反射的應用需要花費更長的啓動時間。

爲了避免這種加載時間的問題,在第一次加載的時候重用了JVM的入口,之後切換到字節碼實現的實現。

上面我們也看到了MethodAccessor實現中有一個Native版本和Java版本。

Native版本一開始啓動快,但是隨着運行時間變長,速度變慢。Java版本一開始加載慢,但是隨着運行時間變長,速度變快。正是因爲兩種存在這些問題,所以第一次加載時使用的是NativeMethodAccessorImpl,而當反射調用次數超過15次之後,則使用MethodAccessorGenerator生成的MethodAccessorImpl對象去實現反射。

最後,我們看一下整個過程的時序圖。

image
原文鏈接:《超車時刻:Java反射源碼解析

《Spring Boot 2.x 視頻教程全家桶》,精品Spring Boot 2.x視頻教程,打造一套最全的Spring Boot 2.x視頻教程。


程序新視界

公衆號“程序新視界”,一個讓你軟實力、硬技術同步提升的平臺

微信公衆號:程序新視界

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章