1. 程式人生 > 程式設計 >Java反射原理分析 - Method篇

Java反射原理分析 - Method篇

反射方法剖析

Class物件提供以下獲取物件的方法(Method):

  • getMethod - 返回類或介面的特定方法,只能是public修飾的方法。
  • getDeclaredMethod - 返回類或介面的特定方法。
  • getMethods - 返回類或介面的所有public方法,包括父類的public方法。
  • getDeclaredMethods - 返回類或介面的所有方法,包括 public、protected、預設(包)訪問和 private 方法,但不包括繼承的方法。

測試用例

//父類
public class RefFather {
    public void refFatherMethod
()
{ System.out.println("我是父類方法 refFatherMethod"); } } //子類 public class RefSon extends RefFather{ private void refSonMethod(){ System.out.println("我是子類方法 refMethod"); } } //測試方法 public static void main(String[] args) { try { //類的全限定名 即包名+類名 Class<?> clz = Class.forName("main.ref.RefSon"
); Object obj = clz.newInstance(); Method refFatherMethod = clz.getMethod("refFatherMethod"); Method refSonMethod = clz.getDeclaredMethod("refSonMethod"); refSonMethod.invoke(obj); } catch (Exception e) { e.printStackTrace(); } } 複製程式碼
Method

先看Class.getMethod(String name,Class<?>... parameterTypes)的原始碼:

    @CallerSensitive
    public Method getMethod(String name,Class<?>... parameterTypes)
        throws NoSuchMethodException,SecurityException {
        checkMemberAccess(Member.PUBLIC,Reflection.getCallerClass(),true);
        Method method = getMethod0(name,parameterTypes,true);
        if (method == null) {
            throw new NoSuchMethodException(getName() + "." + name + argumentTypesToString(parameterTypes));
        }
        return method;
    }
複製程式碼

上面原始碼我們只需要重點解決以下疑問就可以知道 getMethod方法究竟是怎麼實現的。

  1. @CallerSensitive
  2. checkMemberAccess(Member.PUBLIC,true);
  3. Reflection.getCallerClass()
  4. Method method = getMethod0(name,true);
@CallerSensitive

其實這個註解是Java修復漏洞用的。防止使用者使用雙重反射來提升許可權,原理是因為當時反射只檢查深度的呼叫者的類是否有許可權,本來我本身的類是沒有這麼高許可權的,但是我可以通過多重反射來提高呼叫的許可權。

舉個栗子,比如在反射類中某個方法需要向上找固定兩層的呼叫者是否有許可權(反射相關的類許可權是比較高的),我可以通過 我自己的類 -> 反射1 ->反射2這樣的呼叫鏈上,反射2檢查的是反射1的許可權,導致安全漏洞。使用這樣的註解,那麼 getCallerClass 就會直接跳過有 @CallerSensitive 修飾的介面方法,直接查詢真實的呼叫者(actual caller)。

checkMemberAccess

先看這個方法的原始碼:

private void checkMemberAccess(int which,Class<?> caller,boolean checkProxyInterfaces) {
        final SecurityManager s = System.getSecurityManager();
        if (s != null) {
            final ClassLoader ccl = ClassLoader.getClassLoader(caller);
            final ClassLoader cl = getClassLoader0();
            if (which != Member.PUBLIC) {
                if (ccl != cl) {
                    s.checkPermission(SecurityConstants.CHECK_MEMBER_ACCESS_PERMISSION);
                }
            }
            this.checkPackageAccess(ccl,checkProxyInterfaces);
        }
    }
複製程式碼

他的功能主要是檢查是否允許客戶端訪問成員。

預設策略:允許所有客戶端使用普通Java訪問許可權進行訪問控制。

什麼意思呢?

就是如果你沒有主動配置 SecurityManager 安全管理器,則按照Class類的訪問修飾符來判斷是否有許可權。例如 protected 只允許同包,或者子類訪問,跨包則變為 private 禁止訪問。

final ClassLoader ccl = ClassLoader.getClassLoader(caller);

final ClassLoader cl = getClassLoader0();

如果有自定義的 SecurityManager(呼叫 System.setSecurityManager(new MySecurityManager())),就會去判斷呼叫方與訪問方是否具有相同的類載入器,如果有,即便不是public訪問修飾符,也沒有許可權的限制。一般情況下這裡的類載入器 ClassLoader 都是 應用程式類載入器 Application ClassLoader

這裡簡單介紹一下Application ClassLoader :

這個類載入器負責載入使用者類路徑(CLASSPATH)下的類庫,一般我們編寫的java類都是由這個類載入器載入,這個類載入器是CLassLoader中的getSystemClassLoader()方法的返回值,所以也稱為系統類載入器.一般情況下這就是系統預設的類載入器.

Reflection.getCallerClass()

繼續看原始碼:

    /** @deprecated */
    @Deprecated
    public static native Class<?> getCallerClass(int var0);
複製程式碼

native 代表這個方法其實是由其他語言實現的,所以這裡只說明用法。

簡單介紹一下 Reflection.getCallerClass(int var0) ,有興趣的可以瞭解一下。

如果var0 的值 等於0或者小於0,則返回 class sun.reflect.Reflection;

如果var0 的值 等於1,則返回返回自己的類。 即是這行程式碼在哪個類裡面,就返回這個類。

如果var0 的值 等於2,則返回返回呼叫者的類。舉個栗子,這個方法在A的methodA中,在方法B中呼叫A的methodA,這時候 Reflection.getCallerClass(int var0) 返回的就是 class B。

getMethod0(name,true)

繼續原始碼OvO:

private Method getMethod0(String name,Class<?>[] parameterTypes,boolean includeStaticMethods) {
        MethodArray interfaceCandidates = new MethodArray(2);
        Method res =  privateGetMethodRecursive(name,includeStaticMethods,interfaceCandidates);
        if (res != null)
            return res;

        // Not found on class or superclass directly
        interfaceCandidates.removeLessSpecifics();
        return interfaceCandidates.getFirst(); // may be null
    }

 private Method privateGetMethodRecursive(String name,boolean includeStaticMethods,MethodArray allInterfaceCandidates) {
        Method res;
        // Search declared public methods
        if ((res = searchMethods(privateGetDeclaredMethods(true),name,parameterTypes)) != null) {
            if (includeStaticMethods || !Modifier.isStatic(res.getModifiers()))
                return res;
        }
        // Search superclass's methods
        if (!isInterface()) {
            Class<? super T> c = getSuperclass();
            if (c != null) {
                if ((res = c.getMethod0(name,true)) != null) {
                    return res;
                }
            }
        }
        // Search superinterfaces' methods
        Class<?>[] interfaces = getInterfaces();
        for (Class<?> c : interfaces)
            if ((res = c.getMethod0(name,false)) != null)
                allInterfaceCandidates.add(res);
        // Not found
        return null;
    }

	private static Method searchMethods(Method[] methods,String name,Class<?>[] parameterTypes)
    {
        Method res = null;
        String internedName = name.intern();
        for (int i = 0; i < methods.length; i++) {
            Method m = methods[i];
            if (m.getName() == internedName
                && arrayContentsEq(parameterTypes,m.getParameterTypes())
                && (res == null
                    || res.getReturnType().isAssignableFrom(m.getReturnType())))
                res = m;
        }

        return (res == null ? res : getReflectionFactory().copyMethod(res));
    }

  private Method[] privateGetDeclaredMethods(boolean publicOnly) {
        checkInitted();
        Method[] res;
        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
        res = Reflection.filterMethods(this,getDeclaredMethods0(publicOnly));
        if (rd != null) {
            if (publicOnly) {
                rd.declaredPublicMethods = res;
            } else {
                rd.declaredMethods = res;
            }
        }
        return res;
    }
複製程式碼

當呼叫 getMethod0 時,他呼叫了Class 私有的方法 privateGetMethodRecursive 遞迴獲得這個類的所有 public 修飾的方法(包含父類)。

privateGetMethodRecursive 中 會先對當前類所有方法進行匹配,如果不存在則會查詢他的父類,如果父類也沒有則會繼續遞迴,直到頂級父類 Object。如果存在則返回這個Method的副本。

privateGetDeclaredMethods(boolean publicOnly) 引數為true,則返回這個類的所有 public方法,否則返回所有修飾符的方法。

根據privateGetDeclaredMethods,我們不難理解為什麼getMethods()返回的是包含父類的所有public方法,而getDeclaredFields()獲得是當前方法的所有修飾符方法。

大膽推測一下,getMethods()應該是遞迴呼叫 privateGetDeclaredMethods(true)的方法,而getDeclaredMethods()應該只呼叫了一次 privateGetDeclaredMethods(false)的方法。

getMethods

看看原始碼:

    @CallerSensitive
    public Method[] getMethods() throws SecurityException {
        checkMemberAccess(Member.PUBLIC,true);
        return copyMethods(privateGetPublicMethods());
    }
    
    private Method[] privateGetPublicMethods() {
        checkInitted();
        Method[] res;
        ReflectionData<T> rd = reflectionData();
        if (rd != null) {
            res = rd.publicMethods;
            if (res != null) return res;
        }
      
      	//沒有可用的快取值;遞迴計算值。
				//從獲取公共宣告的方法開始
      	MethodArray methods = new MethodArray();
        {
            //請注意這裡
            Method[] tmp = privateGetDeclaredMethods(true);
            methods.addAll(tmp);
        }
        ...
        return res;
    }
複製程式碼
getDeclaredMethods

原始碼如下:

    @CallerSensitive
    public Method[] getDeclaredMethods() throws SecurityException {
        checkMemberAccess(Member.DECLARED,true);
        //請注意這裡
        return copyMethods(privateGetDeclaredMethods(false));
    }
複製程式碼

果然都和預想中的一樣。

最後還有一個getDeclaredMethod 方法沒有介紹,但相信大家看了以上的原始碼一定也能猜到實現的過程了。

    @CallerSensitive
    public Method getDeclaredMethod(String name,SecurityException {
        checkMemberAccess(Member.DECLARED,true);
        Method method = searchMethods(privateGetDeclaredMethods(false),parameterTypes);
        if (method == null) {
            throw new NoSuchMethodException(getName() + "." + name + argumentTypesToString(parameterTypes));
        }
        return method;
    }
複製程式碼

以上所有關於Class的獲取方法原始碼分析都已梳理完畢,~(@^_^@)~喜歡的話還請頂一個贊,您的支援是我更新的最大動力。

由於筆者技術有限,文章難免有些許錯誤,歡迎━(`∀´)ノ指正!

參考資料

jvm註解 @CallerSensitive的用處

大家都說 Java 反射效率低,你知道原因在哪裡麼