1.什么是Classloader
我們知道,我們寫的java程序會被編譯器編譯成class文件運行在jvm虛擬機上的固棚,Classloader就是jvm加載class文件的工具藏澳。
當我們new ClassName()仁锯,或者使用Class.forName("包路徑+類名"),或者使用classloader.loadclass("包路徑+類名")的時候翔悠,就會觸發(fā)Classloader去類加載對應(yīng)的路徑去查找*.class,并創(chuàng)建對象业崖。
正常情況下, 一個classLoader需要兩個必要屬性:
parent: 用于指明當前classLoader的父類加載器.
url: 類命名空間, 用于指明當前classLoader從哪里加載class文件.
2.Classloader的分類
2.1 BootstrapClassloader 啟動類加載器
其實類加載器也是java類,也需要被加載蓄愁。BootstrapClassloader就是最初加載Classloader的加載器双炕。而BootstrapClassloader本身,是JVM啟動時自動加載撮抓,是被C++啟動的妇斤。
BootstrapClassloader 負責加載JDK中的核心類庫,主要是JAVA_HOME/lib下的類丹拯,如rt.jar站超、resources.jar、charsets.jar等乖酬,這里包括其他的java自帶的類加載器死相。
2.2 ExtClassloader 擴展類加載器
主要負責加載Java的擴展類庫,默認加載JAVA_HOME/jre/lib/ext/目下的所有jar包剑刑。
2.3 AppClassloader 應(yīng)用類加載器
主要負責加載classpath下的類,這里包括用戶自己定義的類以及jar包双肤。
2.4 自定義的類加載器
繼承Classloader類實現(xiàn)自己的類加載器施掏,默認父加載器AppClassloader。
這里的父加載器茅糜,并不是繼承關(guān)系
3.Classloader的雙親委派
3.1什么是雙親委派
當一個類需要被加載時七芭,當前的類加載器會把加載任務(wù)委派給parent加載器,parent加載器又會把加載任務(wù)委派給自己parent加載器蔑赘,以此類推狸驳,
直到最頂層bootStrapClassLoader為止, 如果bootStrapClassLoader在自己的類空間(可以理解成上面提到的url)找到了該類的class文件, 就會去加載該類到內(nèi)存中. 如果找不到, 會把任務(wù)再向下傳遞回extClassLoader, 讓它去嘗試加載該類. 依次類推, 直到某個類加載器在自己的類命名空間里找到了該類的class文件, 就會把該類加載進內(nèi)存。
3.2雙親委派機制的用處
雙親委派機制其實是java的一種安全機制缩赛,能保證類在內(nèi)存中只存在一份耙箍,所有程序使用的都是這一份, 不會出現(xiàn)自己定義一個String類, 被自己定義的classLoader給加載進來的情況, 因為rt.jar中的String會被最優(yōu)先加載.
4.自定義Classloader
4.1實現(xiàn)方式
繼承Classloader類,然后重寫findClass方法酥馍,方法內(nèi)去對應(yīng)的路徑下查找class文件辩昆,最后io讀入,用defineClass把byte[] 轉(zhuǎn)化為class對象旨袒。
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class DqClassLoader extends ClassLoader {
private String rootUrl;
public DqClassLoader(String rootPath) {
this.rootUrl = rootPath;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class clazz = null;
//根據(jù)類的二進制名稱,獲得該class文件的字節(jié)碼數(shù)組
byte[] classData = getClassData(name);
if (classData == null) {
throw new ClassNotFoundException();
}
//將class的字節(jié)碼數(shù)組轉(zhuǎn)換成Class類的實例
clazz = defineClass(name, classData, 0, classData.length);
return clazz;
}
private byte[] getClassData(String name) {
InputStream is = null;
try {
String path = classNameToPath(name);
is = new FileInputStream(path);
byte[] buff = new byte[1024 * 4];
int len = -1;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
while ((len = is.read(buff)) != -1) {
baos.write(buff, 0, len);
}
return baos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
private String classNameToPath(String name) {
return rootUrl + "/" + name.replace(".", "/") + ".class";
}
}
4.2 自定義加載器的應(yīng)用場景
最常見的應(yīng)用就是 Apache Tomcat 中的WebClassLoader汁针,每個 Web 應(yīng)用都有一個對應(yīng)的類加載器實例术辐。所不同的是它是首先嘗試去加載某個類,如果找不到再代理給父類加載器施无。這與一般類加載器的順序是相反的辉词。其目的是使得 Web 應(yīng)用自己的類的優(yōu)先級高于 Web 容器提供的類。
當然猾骡,Java 核心庫的類是不在查找范圍之內(nèi)的瑞躺。這也是為了保證 Java 核心庫的類型安全。
5.結(jié)尾
類加載器是 Java 語言的一個創(chuàng)新卓练。它使得動態(tài)安裝和更新軟件組件成為可能隘蝎。
在開發(fā)自己的類加載器的時候,需要注意與已有的類加載器組織結(jié)構(gòu)的協(xié)調(diào)襟企。