深入理解系列之JAVA反射機制

反射是指,程序運行期間,對於任意一個類,都能夠知道這個類的所有屬性和方法,且都能夠調用它的任意一個方法和屬性;這種動態獲取的信息以及動態調用對象的方法的功能稱爲java語言的反射機制。

問題一、反射機制的理論基礎是什麼?

如前言所屬,基本每個java程序員都知道反射的概念和作用,但是爲什麼java可以支持反射?爲什麼C/C++就沒有呢?其實,java之所以能夠實現反射其根本的理論基礎就在於JVM虛擬機中類文件、類加載這兩個重要的特性!
1、類文件是Java支持反射的根本原因:
java雖然也是編譯型語言語言,但是實際上在執行的過程中是採用的是“解釋執行”,這是因爲java的編譯階段只是把源代碼編譯成了一個“中間文件”——我們稱之爲Class文件(二進制文件存儲),Class文件除了存儲與源文件對應的“二進制”信息外,還存儲了在解釋執行期間的加載過程必備的與內存關聯的信息,如屬性、方法該加載到內存區域的哪個地方,以何種數據結構存儲等等,這些所有的信息被稱爲“元信息”。存儲在硬盤上的Class文件是反射能夠實現的重要源頭基礎,但是反射本身並不是直接讀取Class文件來獲取類信息的,這是因爲反射是在運行過程中從內存中獲取的,所以實現反射的直接基礎就是類加載過程!
2、類加載是反射實現的直接因素:
類加載過程包含加載-鏈接(驗證-準備-解析)-初始化-使用-卸載五個大階段,其中第一個階段是反射能夠實現的最直接的因素。在加載過程中,JVM主要做了以下三個工作:
①通過一個類的權限定名來獲取定義此類的二進制流;
②將這個字節流代表的靜態數據結構轉化爲運行時數據結構;
③在內存區生成一個java.lang.Class對象,作爲方法區這個類的各種數據的訪問入口;
我們看到第三個步驟中,在JVM虛擬機中生成了一個被稱之爲Class對象的內存數據,正是因爲加載過程把Class文件信息轉化爲內存中Class對象信息,所以程序在運行期間可以從內存中讀取任意一個類的所有信息,這也就是反射能夠實現的直接原因所在!
我引入網上的一張圖,來闡明類加載過程在內存中的體現:

這裏寫圖片描述
3、動態鏈接階段是反射發揮巨大應用的重要因素:
反射最大的用途並不僅僅就是獲取這些信息並執行,而是聯合類加載過程中“鏈接”階段實現對象、方法、屬性的動態綁定和生成,如我們常見的JDK動態代理就是反射+鏈接作爲理論基礎的!這是因爲“鏈接”階段是在程序運行期間動態執行的,所以我們當然可以在代碼運行期間生成新的類並建立新的鏈接關係!
4、C/C++理論上也可以實現反射:
在C++中,通過RTTI(運行時類型識別),我們也可以知道類的一些信息,但爲什麼C++中卻沒有 Reflection,原因是類型信息不完整。RTTI這個名字本身就告訴我們,C++的類型信息是用來進行類型識別的,因此,它也不需要其它額外的信息。並不是C++無法做到這一點,而是C++不希望給用戶增加額外的負擔。因此,C++放棄了元對象。關於這一點,C++之父 Bjarne Stroustrup在他的《C++語言的設計與演化》的14.2.8節中進行了深入的討論。而且正如上文敘述,僅僅通過反射獲取一些信息並不是我們的終極目的,java的最大好處是可以通過反射+動態鏈接實現新對象、方法、屬性的生成和調用,但是C++作爲一種在編譯階段就已經實現了各個對象之間的連接關係的語言,是無法輕而易舉的實現在運行過程中動態綁定這一特性的!

問題二、反射是如何通過代碼實現的?

這個問題的意義就在於,程序員本身直接接觸的是java代碼,而JVM虛擬機最終打交道其實C/C++以及與之相關的系統調用,那麼這個橋樑是如何建立的呢?這個問題當然得由源碼來解答!
在看源碼之前,我們有必要寫幾個例子來“溫習一下”反射是怎麼用的!

public class Reflect {

  public static void main(String[] args) throws Exception {
    //獲取Class對象
    Class clazz = Class.forName("Student");
    //獲取構造函數
    Constructor constructors = clazz.getConstructor();
    //生成對象
    Object obj = constructors.newInstance();
    //獲取成員變量
    Field name = clazz.getDeclaredField("name");
    //修改成員變量
    name.setAccessible(true);
    name.set(obj, "叉叉叉");
    //獲取方法
    Method method = clazz.getDeclaredMethod("run");
    //執行方法
    method.invoke(obj);
    System.out.println(clazz.getName()+" "+constructors.getName()+" "
            +obj.toString()+" "+name.getName()+" "+method.getName());
  }
}


class Student {
  private String name = "XXXX";
  public Student(){

  }

  public void run(){
    System.out.println("This is "+name);
  }
}

運行的結果如下:
This is 叉叉叉
Student Student Student@677327b6 name run
接着,我們着重選擇Method對象來分析是如何實現反射的:
1、Method的獲取原理:

@CallerSensitive
    public Field getDeclaredField(String name)
        throws NoSuchFieldException, SecurityException {
        checkMemberAccess(Member.DECLARED, Reflection.getCallerClass(), true);
        Field field = searchFields(privateGetDeclaredFields(false), name);
        if (field == null) {
            throw new NoSuchFieldException(name);
        }
        return field;
    }

核心語句就這一句:
Field field = searchFields(privateGetDeclaredFields(false), name);
其中privateGetDeclaredMethods方法的目的從緩存或JVM中獲取該Class已經持有的方法列表,searchMethods方法將從返回的方法列表裏通過循環來匹配方法名稱進而找到一個與之匹配的方法對象。

private Field[] privateGetDeclaredFields(boolean publicOnly) {
        checkInitted();
        Field[] res;
        ReflectionData<T> rd = reflectionData();
        if (rd != null) {
            res = publicOnly ? rd.declaredPublicFields : rd.declaredFields;
            if (res != null) return res;
        }
        // No cached value available; request value from VM
        res = Reflection.filterFields(this, getDeclaredFields0(publicOnly));
        if (rd != null) {
            if (publicOnly) {
                rd.declaredPublicFields = res;
            } else {
                rd.declaredFields = res;
            }
        }
        return res;
    }

我們可以看到,在privateGetDeclaredMethods中首先由reflectionData()獲取元數據,如果獲取的數據不爲空則說明緩存中有數據,則直接取出即可;否則就要從虛擬機中獲取,即向JVM內存中請求元數據!從JVM中獲得方法列表,接下來就開始匹配方法名稱和參數進行提取特定的方法:

private static Field searchFields(Field[] fields, String name) {
        String internedName = name.intern();
        for (int i = 0; i < fields.length; i++) {
            if (fields[i].getName() == internedName) {
                return getReflectionFactory().copyField(fields[i]);
            }
        }
        return null;
    }

如果找到一個匹配的Method,則重新copy一份返回,即getReflectionFactory().copyField(fields[i]);所次每次調用getDeclaredMethod方法返回的Method對象其實都是經過一個新的對象!如果調用的較爲頻繁,建議把Method對象緩存起來。
2、Method的執行原理
方法獲取後,還有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);
    }

應該注意到,實際執行invoke的是methodAccessor類型的對象,實際上methodAccessor的初始化值爲空,需要調用acquireMethodAccessor生成一個新的MethodAccessor對象,MethodAccessor本身就是一個接口,實現如下

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

在acquireMethodAccessor方法中,會通過ReflectionFactory類的newMethodAccessor創建一個實現了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;
    }

如果依然需要更爲詳細的源碼解讀,請參考深入分析Java方法反射的實現原理,同時可以根據自己的需要獨立探究源碼實現,總之反射的源碼實現還是比較複雜的,期間必須涉及Native方法的調用來執行相關的動作

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