反射必殺技:深刻了解Class類,讓你一通百通

1. Class 類的原理

孟子曰:得人心者得天下。而在 Java 中,這個「人心」就是 Class 類,獲取到 Class 類咱們就能夠隨心所欲之隨心所欲。下面讓咱們深刻「人心」,去探索 Class 類的原理。java

首先了解 JVM 如何構建實例。vim

1.1 JVM 構建實例

JVM:Java Virtual Machine,Java 虛擬機。在 JVM 中分爲棧、堆、方法區等,但這些都是 JVM 內存,文中所描述的內存指的就是 JVM 內存。.class 文件是字節碼文件,是經過 .java 文件編譯得來的。數組

知道上面這些內容,咱們開始建立實例。咱們以建立 Person 對象舉例:緩存

Person p = new Person()

簡簡單單經過 new 就建立了對象,那流程是什麼樣的呢?見下圖ide

反射必殺技:深刻了解Class類,讓你一通百通

這也太粗糙了一些,那在精緻一下吧。函數

反射必殺技:深刻了解Class類,讓你一通百通

同志們發現沒有,其實這裏仍是有些區別的,我告訴你區別是下面的字比上面多,你會打我不(別打我臉)。this

粗糙的那個是經過 new 建立的對象,而精緻的是經過 ClassLoader 操做 .class 文件生成 Class 類,而後建立的對象。code

其實經過 new 或者反射建立實例,都須要 Class 對象。對象

1.2 .class 文件

.class 文件在文章開頭講過,是字節碼文件。.java 是源程序。Java 程序是跨平臺的,一次編譯處處執行,而編譯就是從源文件轉換成字節碼文件。blog

字節碼無非就是由 0 和 1 構成的文件。

有以下一個類:

反射必殺技:深刻了解Class類,讓你一通百通

經過 vim 查看一下字節碼文件:

反射必殺技:深刻了解Class類,讓你一通百通

這啥玩意,看不懂。咱也不須要看懂,反正 JVM 對 .class 文件有它本身的讀取規則。

1.3 類加載器

還記得上面的精緻圖中,咱們知道是經過類加載器把 .class 文件加載到內存中。具體的類加載器內容,我會另寫一篇文章講解(寫完連接會更新到這裏)。可是核心方法就是 loadClass(),只須要告訴它要加載的 name,它就會幫你加載:

protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        // 1.檢查類是否已經加載
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                // 2.還沒有加載,遵循父優先的等級加載機制(雙親委派機制)
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }

            if (c == null) {
                // 3.若是尚未加載成功,調用 findClass()
                long t1 = System.nanoTime();
                c = findClass(name);

                // this is the defining class loader; record the stats
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

// 須要重寫該方法,默認就是拋出異常
protected Class<?> findClass(String name) throws ClassNotFoundException {
    throw new ClassNotFoundException(name);
}

類加載器加載 .class 文件主要分位三個步驟

  1. 檢查類是否已經加載,若是有就直接返回
  2. 當前不存在該類,遵循雙親委派機制,加載 .class 文件
  3. 上面兩步都失敗,調用 findClass()

由於 ClassLoader 的 findClass 方法默認拋出異常,須要咱們寫一個子類從新覆蓋它,好比:

@Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            // 經過IO流從指定位置讀取xxx.class文件獲得字節數組
            byte[] datas = getClassData(name);
            if (null == datas){
                throw new ClassNotFoundException("類沒有找到:" + name);
            }
            // 調用類加載器自己的defineClass()方法,由字節碼獲得 class 對象
            return defineClass(name, datas, 0, datas.length);
        }catch (IOException e){
            throw new ClassNotFoundException("類沒有找到:" + name);
        }
    }

    private byte[] getClassData(String name) {
        return byte[] datas;
    }

defineClass 是經過字節碼獲取 Class 的方法,是 ClassLoader 定義的。咱們具體不知道如何實現的,由於最終會調用一個 native 方法:

private native Class<?> defineClass0(String name, byte[] b, int off, int len,
                                         ProtectionDomain pd);

    private native Class<?> defineClass1(String name, byte[] b, int off, int len,
                                         ProtectionDomain pd, String source);

    private native Class<?> defineClass2(String name, java.nio.ByteBuffer b,
                                         int off, int len, ProtectionDomain pd,
                                         String source);

總結下類加載器加載 .class 文件的步驟:

  • 經過 ClassLoader 類中 loadClass() 方法獲取 Class
  • 從緩存中查找,直接返回
  • 緩存中不存在,經過雙親委派機制加載
  • 上面兩步都失敗,調用 findClass()
    • 經過 IO 流從指定位置獲取到 .class 文件獲得字節數組
    • 調用類加載器 defineClass() 方法,由字節數組獲得 Class 對象

1.4 Class 類

.class 文件已經被類加載器加載到內存中並生成字節數組,JVM 根據字節數組建立了對應的 Class 對象。

接下來咱們來分析下 Class 對象。

反射必殺技:深刻了解Class類,讓你一通百通

咱們知道 Java 的對象會有下面的信息:

  1. 權限修飾符
  2. 類名和泛型信息
  3. 接口
  4. 實體
  5. 註解
  6. 構造函數
  7. 方法

這些信息在 .class 文件以 0101 表示,最後 JVM 會把 .class 文件的信息經過它的方式保存到 Class 中。

在 Class 中確定有保存這些信息的字段,咱們來看一下:

反射必殺技:深刻了解Class類,讓你一通百通

Class 類中用 ReflectionData 裏面的字段來與 .class 的內容映射,分別映射了字段、方法、構造器和接口。

反射必殺技:深刻了解Class類,讓你一通百通

經過 annotaionData 映射了註解數據,其它的就不展現了,你們能夠自行打開 IDEA 查看下 Class 的源碼。

那咱們看看 Class 類的方法

1.4.1 構造器

反射必殺技:深刻了解Class類,讓你一通百通

Class 類的構造器是私有的,只能經過 JVM 建立 Class 對象。因此就有了上面經過類加載器獲取 Class 對象的過程。

1.4.2 Class.forName

反射必殺技:深刻了解Class類,讓你一通百通

Class.forName() 方法仍是經過類加載器獲取 Class 對象。

1.4.3 newInstance

反射必殺技:深刻了解Class類,讓你一通百通

newInstance() 的底層是返回無參構造函數。

2. 總結

咱們來梳理下前面的知識點:

反射的關鍵點就是獲取 Class 類,那系統是如何獲取到 Class 類?

是經過類加載器 ClassLoader 將 .class 文件經過字節數組的方式加載到 JVM 中,JVM 將字節數組轉換成 Class 對象。那類加載器是如何加載的呢?

  • 經過 ClassLoader 的 loadClass() 方法
  • 從緩存中查找,直接返回
  • 緩存中不存在,經過雙親委派機制加載
  • 上面兩步都失敗,調用 findClass()
    • 經過 IO 流從指定位置獲取到 .class 文件獲得字節數組
    • 調用類加載器 defineClass() 方法,由字節數組獲得 Class 對象

Class 類的構造器是私有的,因此須要經過 JVM 獲取 Class

Class.forName() 也是經過類加載器獲取的 Class 對象。newInstance 方法的底層也是返回的無參構造函數。

相關文章
相關標籤/搜索