終于有人把Java類加載器講清楚了?。?022最新180分鐘超詳細講解)

代碼拆分
通過將處理邏輯的代碼轉移到jar包中,然后通過UrlClassLoader加載某一個位置的URL(jar包所在的位置),加載生成Class對象,然后通過反射的方式調用方法。

代碼混淆
url可以指定一個網絡地址,不一定是一個本地的目錄。jar包中的class文件可以被反編譯。
修改后綴名
(可以反編譯class文件,但是無法反編譯myclass) -> 自定義類加載器

如何自定義一個類加載器
1. 自定義的SalaryClassloader集成SecureClassloader
自定義一個SalaryClassloader用于加載字節(jié)碼的二進制文件:
2. 重寫findClass方法

將這個字節(jié)碼處理為一個byte數組,然后調用defineClass,返回成Class文件。
3. 存在的問題
myclass中的文件內容沒有被改變,可以稍作處理。比如可以生成一種新的class文件,在文件的第一個位置放入一個值(在讀取的時候也要做對應的處理)。這樣可以避免文件被反編譯
實現一個加載Jar包的類加載器
實現一個SalaryJarLoader ,具體的加載邏輯和直接加載文件,讀取文件字節(jié)流的方式類似。

實現熱加載
生成一個Jar文件修改工資計算的方式,但是再次生成Jar文件覆蓋的時候,修改并沒有生效。每次都需要重啟OA系統(tǒng)。
1. 為什么不會生效
因為會從緩存中加載

2.如何修改 :在方法中生成ClassLoader,而不是在最開始執(zhí)行生成一個。這樣就可以每次清除ClassLoader中的緩存

3. 熱加載機制為什么很少使用?
- 熱加載機制可能出現問題
- 熱加載機制會產生很多的垃圾對象
- 默認不會執(zhí)行鏈接
打破雙親委派
出現的問題:如果我們在OASystem的項目中也添加了一個SalaryCaler類,在后續(xù)加載的過程中,不會加載我們定義在jar中的類。
- 為什么會失?。?/strong>
原因在雙親委派機制上。我們定義的SalaryJarLoader的默認parent是AppClassLoader,AppClassLoader在加載的過程中會首先加載我們定義在OASystem中的SalaryCaler。當SalaryJarLoader在準備加載SalaryCaler時,會在AppClassLoader中找到緩存。這樣一來就不會加載我們定義在jar中的SalaryCaler (這兩個類的全限定名稱是相同的,但是在不同的project中)
解決方式1:通過限定名稱做分支,首先加載自身的SalaryCaler:

解決方式2:解決硬編碼的問題
在上一節(jié)中,雖然可以使用全限定名稱讓SalaryJarLoader優(yōu)先加載定義在jar中的Java文件,而不是首先查看Parent是否加載過。但是使用全限定名稱進行分支判斷的方式存在擴展性的問題。
所以可以重寫ClassLoader#loadClass方法,優(yōu)先執(zhí)行SalaryJarLoader#findClass方法,找到并且返回jar中的Class文件,如果沒有,再去AppClassLoader中加載。
保存多個版本的代碼
使用new創(chuàng)建實例和使用ClassLoader加載一個外部jar包類中的類

會拋出異常:

這是因為兩個類的名字確實相同,但是本質上不是一個類。(可能是為了規(guī)避潛在的類型問題)
只能使用反射的方式:
通過類加載機制構造一個Class對象,然后通過newInstance創(chuàng)建一個Object對象,然后使用Class#getMethod獲取Method,傳入方法名、參數、實例對象 反射調用Method
所以為了同時保存多個版本的代碼,我們首先需要將定義在OASystem同一個project中的SalaryCaler提出,形成一個jar包,并且通過ClassLoader+反射,構造兩個類。
SPI機制完成進一步的優(yōu)化
通過ClassLoader實現的反射的方式可以建立一個Object,但是這種方法不夠優(yōu)雅,我們期望能夠建立一個Interface,然后通過實現接口的方式提供服務。
jdk提供的一種機制。resources下面建立META-INF文件夾,其中再建立services文件夾,在建立一個文件定義加載的內容。
- Jar中:實現一個SalaryCalerService的實現類
在客戶端(也就是需要通過SPI機制注入服務的OASystem類)定義文件內容:

如果有多個實現類,也需要寫。

因為ServiceLoader#load返回的是當前的interface的所有實現類,所以需要使用迭代器模式。默認會從AppClassLoader中加載,如果需要加載我們ClassLoader,使用load的重載即可。
總結
Java類加載機制的模型:
Java的類加載機制分成三個部分:加載-鏈接-初始化 每一個步驟的主要流程如下:

1. 加載過程 Classloader#loadClass
加載過程也是我們可以干預的過程。在加載器中就體現在ClassLoader#loadClass。

2. 雙親委派機制
雙親委派機制不是說一個加載器是另外一個加載的父節(jié)點,而是像鏈表一樣的持有和傳遞的關系。

通常我們寫的類都會使用AppClassLoader進行加載。
3. 自定義一個類加載器
類似于課程中的方法,如果要自定義一個類加載器,因為我們默認使用了雙親委派機制,loadClass的過程不需要我們考慮,我們需要重寫findClass方法。在findClass方法中我們需要讀取某一個地方的Class字節(jié)流文件。因為這個字節(jié)流文件可能是進行加密處理過,我們可以在這里使用某種方式,還原成原本的Class二進制的byte數組,然后傳遞給defineClass。通過defineClass可以返回一個Class對象。

4. 熱加載機制
如果我們想要臨時修改Class文件的執(zhí)行行為,我們直接修改Class文件行不通。
- 文件在ClassLoader執(zhí)行時已經讀取了,后面不會再通過這個路徑繼續(xù)讀取
- 文件已經被讀取到ClassLoader中存在緩存
解決問題1: 在后續(xù)第二次執(zhí)行時,再次讀取Class文件的路徑
解決問題2:需要重新初始化ClassLoader對象
5. 更優(yōu)雅的實現
一個接口存在多個實現類。我們需要借助SPI機制,也就是ServiceLoader接口,對于特定的接口,我們通過META-INF會定義他的實現類名等等。然后我們還可以再ServiceLoader#load時傳入自定義的ClassLoader對象,修改讀取Class實現類的行為。