天天看點

加載成功用不了_Java 是如何加載類的?

加載成功用不了_Java 是如何加載類的?

本文隻是從 Java 的角度出發,并不涉及 Android 的類加載方式。

從上一篇解析類加載機制的文章:

我們已經知道了 ClassLoader 的委托機制。

本篇文章我們來詳細分析下 ClassLoader 是如何加載 Java 類的。

一、ClassLoader 使用

加載成功用不了_Java 是如何加載類的?

流程簡單說是這樣的:

  • 我們用 ClassLoader 的 loadClass() 方法擷取到了對應類的 class 檔案;
  • 随後通過 class 檔案調用 newInstance() 方法建立了對應類的執行個體;
  • 然後調用執行個體的方法;

那麼其實 loadClass() 就是我們加載類的關鍵一步。

二、loadClass() 源碼解析

從上例中,點選 loadClass() 方法,進入 ClassLoader 源碼

加載成功用不了_Java 是如何加載類的?

發現實際起作用的還是 loadClass(name,false) 這個方法,我們點進去看看

加載成功用不了_Java 是如何加載類的?

源碼的解釋已經很清楚了,我們再來看下實際代碼

加載成功用不了_Java 是如何加載類的?

用流程圖可以這樣概括一下

加載成功用不了_Java 是如何加載類的?

我們逐個步驟看下吧

① 保證線程安全

synchronized (getClassLoadingLock(name))
           

給整個 loadClass 的過程加了一把同步鎖,避免了多線程共同加載相同名字的 class 的類加載問題。

② 檢視是否已加載

加載成功用不了_Java 是如何加載類的?
findLoadedClass(name)
           

看了源碼,發現檢視對應名字的 class 是否已被加載是調用的 native 方法:

findLoadedClass0(name)
           

如果 class 已經被加載,那麼就直接傳回加載的 Class 檔案;

如果 class 并未被加載,那麼繼續進行下面的步驟。

③ 查找「父'類加載器」

加載成功用不了_Java 是如何加載類的?

我們再看下全局變量 parent 的聲明

加載成功用不了_Java 是如何加載類的?
加載成功用不了_Java 是如何加載類的?

是以,在加載類時,起初 parent 不為 null,是以會調用

parent.loadClass(name,false)
           

依次往父級上推,直到 parent 為 null,即追溯到的「父'類加載器」是

BootStrap,則會調用

findBootstrapClassOrNull(name)
           

方法,我們來看下這個方法的定義

加載成功用不了_Java 是如何加載類的?

這個方法會傳回一個“被 bootstrap 加載過的類,如果沒有找到,則會傳回 null ”

而真正的邏輯處理也是一個 native 方法:

findBootstrapClass(name)
           

如果這個方法傳回值不為 null ,loadClass 流程會進入:

parent.loadClass(name, false)
           

階段,依次往父級上推,直到出現下方情況之一

  • 加載成功,傳回加載好的類
  • 加載失敗,傳回 null
  • 加載異常,抛出 ClassNotFoundException 異常

則結束此過程。

④ 如果加載失敗,傳回為 null,則會調用:

findClass(name)
           
加載成功用不了_Java 是如何加載類的?

即如果我們沒有自定義類加載器,預設則會抛出

ClassNotFoundException 異常。

ok ,這樣整個過程就結束了。

我們可以看到,整個 loadClass() 的方法會有兩種情況:

  • 加載成功,傳回加載好的類
  • 加載異常,抛出 ClassNotFoundException 異常

三、單純了解了 ClassLoader 中的 loadClass() 不夠,我們來自定義一個類加載器吧

我的例子的思路大緻是:

① 建立一個需要被自定義的 ClassLoader 加載的 java 檔案,并編譯成為 class 檔案

加載成功用不了_Java 是如何加載類的?

很簡單,就是列印一句話,但此例是我們要建立 java 檔案的基類。

下面是我們的需要加載的 java 檔案,即上面基類的子類。

加載成功用不了_Java 是如何加載類的?

運作,得到 class 檔案

加載成功用不了_Java 是如何加載類的?

紅框内即我們得到的class 檔案

② 在我們工程目錄下建立一個新的目錄,用來存放我們建立好的 class 檔案

加載成功用不了_Java 是如何加載類的?

③ 編寫我們的自定義 ClassLoader 檔案

加載成功用不了_Java 是如何加載類的?

對,沒有可擴充性,路徑都是定的。

因為上文中我們解析 loadClass() 方法的源碼時,得知我們需要重寫

findClass

方法,是以這裡就重寫了下,主要功能就是找到我們放到 myclasses 檔案夾下的 class 檔案,并且調用 defineClass 方法去解析出來。

④ 建立運作類,檢視類加載器的加載規則

加載成功用不了_Java 是如何加載類的?

列印結果是

加載成功用不了_Java 是如何加載類的?

我們發現,同樣是通過 myClassLoader 的執行個體加載的類,但是當我們加載

MyClassLoaderTest 時:

Class loadClass = classLoader.loadClass("com.guaju.classloadertest.MyClassLoaderTest");
           

真正的類加載器是 AppClassLoader

而當我們加載 PrintUtil 時:

Class> loadClass = myClassLoader.loadClass("PrintUtil");
           

真正的類加載器是 MyClassLoader。

當我們修改 PrintUtils 的加載方式時

加載成功用不了_Java 是如何加載類的?

真正的類加載器也是 AppClassLoader

加載成功用不了_Java 是如何加載類的?

總結得到這兩點:

  • 自定義類加載器時,如果傳入完整類名,會優先使用系統類加載器去加載類,如果系統類加載器找不到該類,則會調用自定義的類加載器。
  • 自定義類加載器時,需要使用 findClass 去定位需要加載的類,讀取并調用 defineClass 方法去解析類

好了,本篇完~~~

後續會繼續針對 Android 項目的類加載進行解析,如果有興趣想繼續看,點個關注吧~