本文隻是從 Java 的角度出發,并不涉及 Android 的類加載方式。
從上一篇解析類加載機制的文章:
我們已經知道了 ClassLoader 的委托機制。
本篇文章我們來詳細分析下 ClassLoader 是如何加載 Java 類的。
一、ClassLoader 使用
流程簡單說是這樣的:
- 我們用 ClassLoader 的 loadClass() 方法擷取到了對應類的 class 檔案;
- 随後通過 class 檔案調用 newInstance() 方法建立了對應類的執行個體;
- 然後調用執行個體的方法;
那麼其實 loadClass() 就是我們加載類的關鍵一步。
二、loadClass() 源碼解析
從上例中,點選 loadClass() 方法,進入 ClassLoader 源碼
發現實際起作用的還是 loadClass(name,false) 這個方法,我們點進去看看
源碼的解釋已經很清楚了,我們再來看下實際代碼
用流程圖可以這樣概括一下
我們逐個步驟看下吧
① 保證線程安全
synchronized (getClassLoadingLock(name))
給整個 loadClass 的過程加了一把同步鎖,避免了多線程共同加載相同名字的 class 的類加載問題。
② 檢視是否已加載
findLoadedClass(name)
看了源碼,發現檢視對應名字的 class 是否已被加載是調用的 native 方法:
findLoadedClass0(name)
如果 class 已經被加載,那麼就直接傳回加載的 Class 檔案;
如果 class 并未被加載,那麼繼續進行下面的步驟。
③ 查找「父'類加載器」
我們再看下全局變量 parent 的聲明
是以,在加載類時,起初 parent 不為 null,是以會調用
parent.loadClass(name,false)
依次往父級上推,直到 parent 為 null,即追溯到的「父'類加載器」是
BootStrap,則會調用
findBootstrapClassOrNull(name)
方法,我們來看下這個方法的定義
這個方法會傳回一個“被 bootstrap 加載過的類,如果沒有找到,則會傳回 null ”
而真正的邏輯處理也是一個 native 方法:
findBootstrapClass(name)
如果這個方法傳回值不為 null ,loadClass 流程會進入:
parent.loadClass(name, false)
階段,依次往父級上推,直到出現下方情況之一
- 加載成功,傳回加載好的類
- 加載失敗,傳回 null
- 加載異常,抛出 ClassNotFoundException 異常
則結束此過程。
④ 如果加載失敗,傳回為 null,則會調用:
findClass(name)
即如果我們沒有自定義類加載器,預設則會抛出
ClassNotFoundException 異常。
ok ,這樣整個過程就結束了。
我們可以看到,整個 loadClass() 的方法會有兩種情況:
- 加載成功,傳回加載好的類
- 加載異常,抛出 ClassNotFoundException 異常
三、單純了解了 ClassLoader 中的 loadClass() 不夠,我們來自定義一個類加載器吧
我的例子的思路大緻是:
① 建立一個需要被自定義的 ClassLoader 加載的 java 檔案,并編譯成為 class 檔案
很簡單,就是列印一句話,但此例是我們要建立 java 檔案的基類。
下面是我們的需要加載的 java 檔案,即上面基類的子類。
運作,得到 class 檔案
紅框内即我們得到的class 檔案
② 在我們工程目錄下建立一個新的目錄,用來存放我們建立好的 class 檔案
③ 編寫我們的自定義 ClassLoader 檔案
對,沒有可擴充性,路徑都是定的。
因為上文中我們解析 loadClass() 方法的源碼時,得知我們需要重寫
findClass
方法,是以這裡就重寫了下,主要功能就是找到我們放到 myclasses 檔案夾下的 class 檔案,并且調用 defineClass 方法去解析出來。
④ 建立運作類,檢視類加載器的加載規則
列印結果是
我們發現,同樣是通過 myClassLoader 的執行個體加載的類,但是當我們加載
MyClassLoaderTest 時:
Class loadClass = classLoader.loadClass("com.guaju.classloadertest.MyClassLoaderTest");
真正的類加載器是 AppClassLoader
而當我們加載 PrintUtil 時:
Class> loadClass = myClassLoader.loadClass("PrintUtil");
真正的類加載器是 MyClassLoader。
當我們修改 PrintUtils 的加載方式時
真正的類加載器也是 AppClassLoader
總結得到這兩點:
- 自定義類加載器時,如果傳入完整類名,會優先使用系統類加載器去加載類,如果系統類加載器找不到該類,則會調用自定義的類加載器。
- 自定義類加載器時,需要使用 findClass 去定位需要加載的類,讀取并調用 defineClass 方法去解析類
好了,本篇完~~~
後續會繼續針對 Android 項目的類加載進行解析,如果有興趣想繼續看,點個關注吧~