定義
根據《深入理解Java虛擬機器》提到“通過一個類的全限定名(packageName.ClassName)來獲取描述此類的二進位制位元組(class檔案位元組)這個動作的程式碼模組就叫做類載入器(ClassLoader)”。
作用
1、通常類載入器的作用是載入資源(位元組碼檔案)到java虛擬機器中,想要在一個jvm 程式中唯一確認一個類,除了類的全限定名外,還需要指定它是由哪個類載入器載入的。
2、比如我們的類庫需要通過遠端網路獲取,可以通過自定義類載入器從遠端載入位元組碼檔案。
3、java的位元組碼檔案很容易反編譯出來,一些核心的程式碼不想被反編譯出來,可以對位元組碼進行加密,然後通過自定義的類載入器載入這些位元組碼,然後進行解碼返回給虛擬機器。
4、比如jvm的熱載入和熱部署等功能也需要自定義類載入器來完成。
.......
java類載入器的種類
1、Bootstrap ClassLoader : 該載入器是最頂層的類載入器,它是載入放在{Java_home}\lib目錄 或者-Xbootclasspath指定路徑下類庫。
2、Extension ClassLoader : 該類載入器負載載入{Java_home}/lib\ext目錄 或者System.getenv("java.ext.dirs")系統變數路徑下的類庫。
3、Application ClassLoader : 該類載入器載入使用者程式類路徑下的類庫,它是預設的程式的類載入器。
雙親委派機制
1、雙親委派機制,雙親委派除了啟動類載入器(Bootstrap ClassLoader)外,其他的類載入器都應該有自己父載入器。它們的實現不是通過繼承來實現的,而是通過組合的方式。當載入某個Class時,當前類載入器會把這個載入請求委派給其父類載入器載入,同理父類載入器同樣委派其它的父類載入器載入,直到無其父類類載入器載入為止,如果父類載入器載入失敗,才會由其子類載入。
2、使用雙親委派機制,有個明顯的特徵是:Java類隨著它的類載入器一起具備了一種優先順序的層次關係。比如rt.jar包中的java.lang.Object,由於它所在位置是由啟動類載入器載入,所以Object類在程式的各種類載入器環境中都是同一個類。
3、雙親委派模型如下:
ClassLoader
可以看下ClassLoader 雙親委派模型的大致程式碼框架如下:
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 {
//3、沒有父類載入器,委託給BootstrapClassLoader
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// 父類載入器沒有載入到,則自己載入
long t1 = System.nanoTime();
c = findClass(name);
// 記錄該類載入的狀態Stat.
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
// resolve :true,需要對類進行連結(連結階段包括:準備,解析,初始化類)
if (resolve) {
resolveClass(c);
}
return c;
}
}
1、通過以上可以知道,我們可以繼承ClassLoader 來實現自己的類載入器,然後重寫findClass()方法,這些還是儲存了雙親委派機制。
2、當我們重寫findClass()方法時,得到該類的位元組碼後,需要呼叫defineClass()來放回Class<?>物件。
URLClassLoader
1、一般自定義的類載入器可以直接繼承該類,該類載入器通過新增url路徑來獲取類。
2、可以在其建構函式上URLClassLoader(URL[] urls, ClassLoader parent)直接進行新增其URL。
3、也可以通過addURL(URL)新增其URL。
自定義類載入器
1、首先假設main-project為我們自己編寫的工程,其依賴某一api:service-spi。而service-spi的實現有很多,project-spi-impl是其中的一個。
2、當main-project僅依賴service-spi,而project-spi-impl不在工程的類載入路徑下。所以需要自定義類載入器,從某個路徑下的jar載入進來。CoreClassLoader就是自定義的類載入器。
3、CoreClassLoader繼承自URLClassLoader,然後把相關搜尋路徑新增到該類載入器即可。
4.github:https://github.com/zhvqee/class-loader
SpringBoot 對類載入器的運用
1、SpringBoot 工程通過spring-boot-maven-plugin外掛打包。把相關資源和依賴包都打到一個jar包中(all in one)。其包的結構如下:
BOOT-INF/classes:存放的是本工程的class檔案
BOOT-INF/lib:存放的是本工程依賴的二方包和三方包。
META-INF/MANIFEST.MF:該檔案記錄了程式啟動入口等。
o.s.b.loader包下就是springboot自定義的類載入器。
2、當我們執行java -jar xxxx 時,會讀取MANIFEST.MF下
Main-Class: org.springframework.boot.loader.JarLauncher,
該配置就是程式的路口。而我們編寫的Main方法不是真正的啟動的入口。
3、當執行Launcher#launch()方法時,會把SpringBoot自定義的類載入器LaunchedURLClassLoader設定執行緒的上下文中,並通過該自定義類載入器載入我們自己編寫的main方法所在的類,然後利用反射呼叫main方法。程式碼片段如下:
Class<?> mainClass = Thread.currentThread().getContextClassLoader().loadClass(this.mainClassName);
Method mainMethod = mainClass.getDeclaredMethod("main", String[].class);
mainMethod.invoke(null, new Object[] { this.args });
4、LaunchedURLClassLoader 類載入器繼承URLClassLoader類載入器,它載入的路徑就是可執行jar下BOOT-INF下的class檔案和二方包、三方包。
Class.forName() 和 ClassLoader.load()
1、JVM 載入的類主要經過以下幾個步驟:載入,連結,初始化,試用,解除安裝。
2、Class.forName()預設是需要對載入的類進行初始化。
3、ClassLoader.load實際呼叫的是ClassLoader.load(className,false),false:表示不進行連結,不進行連結也就代表不會進行初始化的操作,類的靜態塊和靜態物件都不會執行。
該文章