java反射機制源碼分析

前言

我們先對前幾天的學習進行總結,前幾天我們主要結合源碼學習了java中的集合,重點分析了HashMap散列桶的實現,還讓大夥兒去看紅黑樹。今天就來學習java反射相關的東西,反射可是java一個很重要的高級特性,很多框架都是基於反射實現的,提高對反射相關機制的瞭解也有利於我們將來造輪子。接下來我們結合源碼以及java虛擬機來分析反射。

定義

JAVA反射機制是在運行狀態中,對於任意一個實體類,都能夠知道這個類的所有屬性和方法;對於任意一個對象,都能夠調用它的任意方法和屬性;這種動態獲取信息以及動態調用對象方法的功能稱爲java語言的反射機制。這就意味着我們在對一個類未知的情況下可以通過多種方式獲取類,調用方法和屬性,這是JDK中動態代理實現的基礎,當然還有別的動態代理實現的方式,例如CGLIB等

基礎複習

不知道各位看官對反射的基本使用忘記沒有,我在這裏帶大家複習一下。

首先,我們都知道java中的實例對象都是通過對應的Class對象創建的,而Class對象的加載在我的java虛擬機類加載機制中已經有了分析,感興趣的可以看https://blog.csdn.net/qq_41861259/article/details/103050011。所以如果我們要操作一個對象,首先要獲取到他的Class對象,那麼獲取Class對象的方式有哪些呢?主要有三種方式,如代碼所示:
 

//通過class常量獲取
Class c = Integer.class;
//通過類的全限定名獲取
Class c1 = Class.forName("java.lang.Integer");
//通過實例獲取Class對象
Integer integer = new Integer(1);
integer.getClass();

當我們獲取到Class對象,那麼我們要操作一個實例對象還需要什麼呢?那就是方法,我們通過Class對象得到實例,通過方法名獲取方法,調用方法,當然我們的構造方法和私有方法也都是可以獲取的,需要設置setAccessible(true),還可以通過getSuperclass()獲取父類

//得到Class對象之後
//獲取方法,指定方法名和參數
 Method method = c.getMethod(”方法名“,"參數“);
//實例化對象
 Object o = c.newInstance();
//調用invoke方法執行方法
 method.invoke(o,"hahah");

正題

先看下反射的整體框架,這是我自己畫的,也就這個水平了,哈哈!其實最開始我的想法是通過Method、Filed這兩個類成員來展開畫正題的框架,並且實際畫的過程中兩者也都整體對稱的感覺,但是當加入一些依賴的時候,漸漸的Reflection就成爲了中心,此時真是感概那些大佬的設計啊!!!

我們先來看Class對象的獲取,這裏我使用Class的forName方法Class c = Class.forName("com.reflect.Person");
我們先補充說明下@CallerSensitive註解,我在網上一個解釋:

這個註解是爲了堵住漏洞用的。曾經有黑客通過構造雙重反射來提升權限,
原理是當時反射只檢查固定深度的調用者的類,看它有沒有特權,
例如固定看兩層的調用者(getCallerClass(2))。如果我的類本來沒足夠
權限羣訪問某些信息,那我就可以通過雙重反射去達到目的:反射相關
的類是有很高權限的,而在 我->反射1->反射2 這樣的調用鏈上,反射2
檢查權限時看到的是反射1的類,這就被欺騙了,導致安全漏洞。
使用CallerSensitive後,getCallerClass不再用固定深度去尋找
actual caller(“我”),而是把所有跟反射相關的接口方法都標註上
CallerSensitive,搜索時凡看到該註解都直接跳過,這樣就有效解決了
前面舉例的問題

通過native方法forName0()獲取到我們需要的Class對象,然後通過這個對象獲取方法,例如:Method method = c.getMethod("getUsername");

然後進入我們的getMethod0方法,其中MethodArray這個靜態內部類就是維護了一個Method數組

深入到此都沒有看到具體獲取method方法的實現,但是不要着急,我們繼續看,註釋我已經寫在代碼中,總體來說,我們是通過查詢ReflectionData而得到的,這個私有的靜態內部類中有我們需要的很多信息,如代碼後的貼圖所示:
 

private Method privateGetMethodRecursive(String name,
            Class<?>[] parameterTypes,
            boolean includeStaticMethods,
            MethodArray allInterfaceCandidates) {
        // 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, parameterTypes, true)) != null) {
                    return res;
                }
            }
        }
        //查詢父類接口中的方法
        // Search superinterfaces' methods
        Class<?>[] interfaces = getInterfaces();
        for (Class<?> c : interfaces)
            if ((res = c.getMethod0(name, parameterTypes, false)) != null)
                allInterfaceCandidates.add(res);
        // Not found
        return null;
    }


一直到這裏我們只是知道了方法的獲取,那麼方法是如何執行的?大家一定要耐心看,我們在開始查詢方法的時候,將方法的Class對象以及方法的信息都記錄了下來,下面我們來調用invoke方法method.invoke(c)

分析到這裏過後,就主要是我們的MethodAccessor的具體實現了。

invoke:在其內部交給MethodAccessor處理,而這個接口又有兩種實現,本地方法來實現反射調用 和委派模式,method實例的第一次反射調用會生成一個委派實現,具體的就是本地實現(進入虛擬機內部以後,便擁有了Methos實例所指向方法的具體地址,然後就可以調用目標方法本地方法來實現反射調用,其中調用的順序:

  • Method.invoke
  • 委派實現(DelegatingMethodAccessorImpl)
  • 本地方法(NativeMethodAccessorImpl),到達目標方法採用委派做中間層

那麼採用委派做中間層,爲啥要用委派,不直接調用本地方法?

  • 其實,Java的反射調用機制還設立了另一種動態生成字節碼的實現(動態實現),直接使用invoke指令調用目標方法,採用委派方便本地實現和動態實現中切換

  • 動態實現效率比本地方法快幾十倍,因爲動態不需要java中調用C++,在回到java,但是生成字節碼非常耗時,調用一次的話,本地放法快三四倍

  • Inflation :許多反射僅調用一次,所以設置了一個閥值15,達到十五就動態生成字節碼,委派模式切換爲動態實現,調用GeneratedMethodAccessor1

4. 反射調用的開銷

  • Class.forName會調用本地方法,Class.getMethod 會遍歷類的所有共有方法,甚至是父類方法

  • getMethod爲代表的查找方法會返回結果的Copy,要避免在熱點代碼中使用Method,返回數組的方法

  • Method.invoke是一個可變長參數方法,字節碼層面他的最後一個參數會是Object數組,Java編譯器會在調用放出生成一個長度爲傳入參數數量的Object數組,切Object數組不能存儲基本類型Java編譯器還要對基本類型進行封裝,性能開銷,還佔用堆內存,Gc更加頻繁

最後屬性的獲取就相對簡單一些了,方法與Method使用的都差不多,這裏就不多說了,最後祝大家學習愉快

發佈了37 篇原創文章 · 獲贊 40 · 訪問量 2919
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章