??不論人生多不幸火诸,聰明的人總會從中獲得一點利益抚官;不論人生多幸福哀军,愚蠢的人總覺得無限悲哀。 —— 拉·羅休弗克
??通過一個類的全限定名來獲取描述此類的二進制字節(jié)流這個動作放到Java虛擬機外部去實現(xiàn)球匕,以便讓應(yīng)用程序自身決定如何獲取所需要的類。實現(xiàn)這個動作的代碼模塊稱為"類加載器"帖烘。
類與類加載器
??對于任意一個類亮曹,都需要由加載它的類加載器和這個類本身一同確立其在Java虛擬機中的唯一性。更通俗一點的說法:比較兩個類是否"相等",只有在這兩個類是由同一個類加載器加載的前提下才有意義乾忱,否則讥珍,即使這兩個類來源于同一個class文件,被同一個虛擬機加載窄瘟,只要加載它們的類加載器不一樣衷佃,那這兩個類就必定不相等。
??這里的"相等"蹄葱,包括代表類的Class對象的equals()方法氏义、isAssignableForm()方法、isInstance()方法的返回結(jié)果图云,也包括使用instanceof關(guān)鍵字做對象所屬關(guān)系判斷等惯悠。
public class ClassLoaderTest {
public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
ClassLoader classLoader = new ClassLoader(){
@Override
public Class<?> loadClass(String name)
throws ClassNotFoundException {
try {
String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
InputStream is = getClass().getResourceAsStream(fileName);
if(null == is){
return super.loadClass(name);
}
byte[] b = new byte[is.available()];
is.read(b);
return defineClass(name, b, 0, b.length);
} catch (IOException e) {
throw new ClassNotFoundException(name);
}
}
};
Object obj = classLoader.loadClass("com.xx.java.ClassLoaderTest").newInstance();
System.out.println(obj.getClass());
System.out.println(obj instanceof com.xx.java.ClassLoaderTest);
}
}
運行結(jié)果:
class com.xx.java.ClassLoaderTest
false
??從運行結(jié)果來看,第一句返回的是class com.xx.java.ClassLoaderTest竣况,說明這個對象確實是com.xx.java.ClassLoaderTest的實例化對象克婶,而在用instanceof進行類所屬關(guān)系判定時,返回的結(jié)果是false丹泉,這是因為虛擬機中存在了兩個ClassLoaderTest情萤,一個是由系統(tǒng)應(yīng)用程序類加載器加載的,另外一個是由我們自定義的類加載器加載的摹恨,雖然來自于同一個class文件筋岛,但依然是兩個獨立的類。
雙親委派模型
??從Java虛擬機來講晒哄,只存在兩種類加載器睁宰,一種是啟動類加載器(Bootstrap ClassLoader),另一種就是所有其他的類加載器寝凌,并且全部繼承抽象類java.lang.ClassLoader柒傻。
??從Java開發(fā)人員來講,類加載器可分為以下三種類加載器:
- 啟動類加載器(Bootstrap ClassLoader):該類加載器負責(zé)將存放在<JAVA_HOME>\lib目錄中的硫兰,或者被-Xbootclasspath參數(shù)所指定的路徑中的诅愚,并且可被虛擬機識別的類庫加載到虛擬機。
- 擴展類加載器(Extension ClassLoader):該類加載器由sun.misc.Launcher$ExtClassLoader實現(xiàn)劫映,負責(zé)加載<JAVA_HOME>\lib\ext目錄中的违孝,或被java.ext.dirs系統(tǒng)變量所指定的路徑中的所有類庫。
- 應(yīng)用程序類加載器(Application ClassLoader):該類加載器由sun.misc.Launcher$AppClassLoader實現(xiàn)泳赋。負責(zé)加載Classpath上所指定的類雌桑。
-
自定義類加載器
??除了以上3種類加載器外,開發(fā)人員還可以自定義類加載器祖今。實現(xiàn)自定義類加載有兩種方式:重寫ClassLoader類的findClass()方法或loadClass()方法校坑。 - 重寫loadClass()方法:
??如果要想在JVM的不同類加載器中保留具有相同全限定名的類拣技,那就要通過重寫loadClass來實現(xiàn)。實現(xiàn)方式是:首先通過用戶自定義的類加載器來判斷該類是否可加載耍目,如果可以加載就由自定義的類加載器進行加載膏斤,如果不能夠加載才交給父類加載器去加載。 這種情況下邪驮,就有可能有大量相同的類莫辨,被不同的自定義類加載器加載到JVM中。
ClassLoader loadloader = new ClassLoader() {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
try {
String filename = name.replace('.', '/') + ".class";
InputStream is = getClass().getResourceAsStream(filename);
if (is == null) {
return super.loadClass(name);
}
byte[] bt = new byte[is.available()];
is.read(bt);
return defineClass(name, bt, 0, bt.length);
} catch (IOException e) {
e.printStackTrace();
}
return super.loadClass(name);
}
}
- 重寫findClass()方法
??重寫findClass方法的自定義類毅访,首先會通過父類加載器進行加載沮榜,如果所有父類加載器都無法加載,再通過用戶自定義的findClass方法進行加載喻粹。如果父類加載器可以加載這個類或者當前類已經(jīng)存在于某個父類的容器中了蟆融,這個類是不會再次被加載的,此時用戶自定義的findClass方法就不會被執(zhí)行守呜,保證了相同全限定名的類是不會被重復(fù)加載到JVM中型酥。
ClassLoader findloader = new ClassLoader() {
@Override
public Class<?> findClass(String name) throws ClassNotFoundException {
try {
String filename = name.replace('.', '/') + ".class";
InputStream is = getClass().getResourceAsStream(filename);
if (is == null) {
return super.findClass(name);
}
byte[] bt = new byte[is.available()];
is.read(bt);
return defineClass(name, bt, 0, bt.length);
} catch (IOException e) {
e.printStackTrace();
}
return super.findClass(name);
}
}
??下圖為雙親委派模型的工作示意圖:
??雙親委派模型的工作過程:當一個類加載器收到類加載請求,它首先將這個請求委派給父類加載器去完成查乒,自己不會去加載冕末,每一個層次的類加載器都是如此。只有當父類加載器無法完成加載請求時侣颂,子類加載器才會嘗試自己加載。
??使用雙親委派模型的好處在于Java類隨著它的類加載器一起具備了一種帶有優(yōu)先級的層次關(guān)系枪孩。例如類java.lang.Object憔晒,它存在在rt.jar中,無論哪一個類加載器要加載這個類蔑舞,最終都是委派給處于模型最頂端的Bootstrap ClassLoader進行加載拒担,因此Object類在程序的各種類加載器環(huán)境中都是同一個類。相反攻询,如果沒有雙親委派模型而是由各個類加載器自行加載的話从撼,如果用戶編寫了一個java.lang.Object的同名類并放在ClassPath中,那系統(tǒng)中將會出現(xiàn)多個不同的Object類钧栖。
雙親委派模型的系統(tǒng)實現(xiàn)
??在java.lang.ClassLoader的loadClass()方法中低零,先檢查是否已經(jīng)被加載過,若沒有加載則調(diào)用父類加載器的loadClass()方法拯杠,若父加載器為空則默認使用啟動類加載器作為父加載器掏婶。如果父加載失敗,則拋出ClassNotFoundException異常后潭陪,再調(diào)用自己的findClass()方法進行加載雄妥。
protected synchronized Class<?> loadClass(String name,boolean resolve)throws ClassNotFoundException{
// First, check if the class has already been loaded
Class c = findLoadedClass(name);
if(c == null){
try{
if(parent != null){ // use parent classloader load class
c = parent.loadClass(name,false);
}else{ // use bootstrap classloader load class
c = findBootstrapClassOrNull(name);
}
}catch(ClassNotFoundException e){
//if throws the exception ,the father can not complete the load
}
if(c == null){// If still not found, then invoke findClass in order to find the class
c = findClass(name);
}
}
if(resolve){
resolveClass(c);
}
return c;
}