反射從入門到精通之深刻了解Class類

知其然,知其因此然

0. 前言

本文會講解反射的原理,若是你們對反射不瞭解,能夠先看《反射從0到入門》,對反射有大概的瞭解。java

《反射從入門到精通》我會分爲兩篇來說解,這一篇是講解 Class 類的原理,下一篇我會講解反射 API 的原理。vim

1. Class 類的原理

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

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

1.1 JVM 構建實例

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

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

Person p = new Person()

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

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

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

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

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

1.2 .class 文件

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

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

有以下一個類:

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

這啥玩意,看不懂。咱也不須要看懂,反正 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 對象。

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

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

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

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

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

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

那咱們看看 Class 類的方法

1.4.1 構造器

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

1.4.2 Class.forName

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

1.4.3 newInstance

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

2. 總結

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

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

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

  • 經過 ClassLoaderloadClass() 方法

    • 從緩存中查找,直接返回
    • 緩存中不存在,經過雙親委派機制加載
    • 上面兩步都失敗,調用 findClass()

      • 經過 IO 流從指定位置獲取到 .class 文件獲得字節數組
      • 調用類加載器 defineClass() 方法,由字節數組獲得 Class 對象

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

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


相關文章
相關標籤/搜索