類加載器--5
自定義類加載器
自定義類加載器舉例
----------- android培訓、java培訓、java學習型技術博客、期待與您交流! ------------
1. 自定義類加載器
1). 類加載過程可操控部分 (回顧)
(1). 類加載過程的五個環節
[1]. 一個類被加載的五個階段:加載、驗證、準備、解析和初始化。
[2]. 只有加載階段中的環節可以被編程人員進行自定義編碼
(2). 類加載的加載階段中具體可控環節
[1]. JVM並沒有對這條規範中“從哪裏獲取此類的二進制流”並且“怎樣獲取此類的二進制流”做出明確的規定。
[2]. 設計JVM的團隊將“通過全類名獲取定義此類的二進制字節 (byte) 流”這個動作放到JVM的外部去實現,以便讓應用程序自己決定如何去獲取所需要的類。
[3]. 實現“通過全類名獲取定義此類的二進制字節 (byte) 流”這個動作代碼的模塊稱爲類加載器。
(3). 加載階段的代碼對應
以上加載階段的代碼主要對應的就是java.lang.ClassLoader中的protectedClass<?> loadClass(String name, boolean resolve)方法
2). findClass( )和 loadClass( )方法的關係
(1). findClass() 和loadClass() 出現的時機
[1]. loadClass( )方法出現的版本JDK1.0
[2]. findClass( )方法出現在JDK1.2以後
(2). 引入findClass( )方法的目的
[1]. 類加載器的雙親委託機制是在JDK1.2引入的
[2]. 爲了向前兼容並遵循JDK1.2引入的雙親委託機制,這樣自定義的類加載器的內容應該寫道這個findClass方法中。
[3]. loadClass( )會調用findClass方法。
3). 自定義類加載器
(1). 自定義類加載器的方式
[1]. 繼承java.lang.ClassLoader直接覆蓋ClassLoader抽象父類中loadClass方法
[2]. 繼承java.lang.ClassLoader直接覆蓋ClassLoader抽象父類中findClass方法
(2). 兩種自定義類加載器的方法的比較
[1]. 如果採取覆蓋ClassLoader抽象父類中loadClass方法,就要自己重新編寫雙親委託機制,這樣勢必非常麻煩。
[2]. JDK1.2以後不再提倡直接覆蓋loadClass方法,而是應當把自己的類加載邏輯寫到findClass方法中。在loadClass()方法的邏輯中如果父類加載失敗,就會調用到自定義的findClass方法中的內容。這樣就能保證自定義的類加載器符合雙親委託機制。
(3). 自定義類加載器的步驟
[1]. 新建類繼承java.lang.ClassLoader抽象類
[2]. 子類重寫ClassLoader類的findClass( )方法【通常做法】
findClass原型:protected Class<?> findClass(String name);
[2] 1. 按照自己的邏輯獲取字節碼數據
[2]2. 將獲取到的字節碼數據轉換爲對應的字節數組(byte[])
[2]3. 調用ClassLoader的defineClass(byte[], int off, int length)方法將字節碼對應的byte[]數據作爲實參傳給defineClass方法並將defineClass構建的Class對象返回。【因爲findClass方法要求返回Class對象】
(4). 自定義類加載器的位置
[1]. 自定義類加載器本身是java.lang.ClassLoader的直接子類。
【注意】類加載器本身之間的“父子”關係並不是通過extends關鍵字來表達的,而是通過繼承java.lang.ClassLoader的parent屬性指向不同類加載器構建類加載器之間的關係。
[2]. 測試自定義類加載器在類加載器的結構中的位置
{1}. 自定義類加載器
public class MyClassLoader extends ClassLoader {}
{2}. 測試代碼
public class ClassLoaderTest {
publicstatic voidmain(String[] args) {
ClassLoader loader =new MyClassLoader();
while(loader!= null){
System.out.println(loader);
loader =loader.getParent();
}
}
}
{3}. 打印結果
從打印結果上來看:直接繼承的類加載器MyClassLoader的parent屬性直接指向了AppClassLoader的實例
【疑問】這個MyClassLoader的parent是怎麼被指定的呢?
分析:由於parent是MyClassLoader從java.lang.ClassLoader類中繼承來的一個成員屬性,僅僅是在被new之後就能打印出這樣的組織關係。推斷:一定是在MyClassLoader的無參構造方法中調用的無參父類構造方法【全部隱式調用】指定了這個MyClassLoader的實例的parent屬性。
[3]. 自定義類加載器和系統默認類加載器的關聯紐帶 --- ClassLoader的構造方法
{1}.ClassLoader的無參構造方法
protectedClassLoader() {
this(checkCreateClassLoader(),getSystemClassLoader());
}
這個方法又調用了另一個重載的構造方法,傳入的第二個參數是 getSystemClassLoader()的返回值。這個方法的返回值就是系統類加載器,就是AppClassLoader類加載器。
{2}.ClassLoader的有參構造方法
privateClassLoader(Void unused, ClassLoader parent) {
this.parent= parent;
//…
}
【結論】自定義類加載器在new的過程中會自動調用父類ClassLoader的構造方法。父類的這個構造方法會調用另一個重載的構造方法,這個構造方法將自定義的類加載器的parent屬性直接指向了AppClassLoader類加載器。
所以一般情況下定義的類加載器實例的“父類加載器”【指的是parent指向的實例】都是AppClassLoader。
【這樣即使使用自定義的類加載器加載類的時候,也一定遵循類加載器的雙親委託機制】
2. 自定義類加載器舉例
1). 在所有默認類加載器管轄範圍之外放置一個字節碼文件
(1). 自定義代碼 -----放置到默認類加載器管轄範圍之外
[1]. OutClass源代碼
public class OuterClass{
publicvoid showOuter(){
System.out.println("Show Outer...");
}
[2]. 對OutClass.class打成out.jar包+ OutClass.class本身 一同放置到工程的lib文件夾下,如圖:
[3]. 將out.jar引入工程ClassLoader中:
【這樣所有的默認類加載器無法自動所尋到“D:\BlackHorse_Advanced\ClassLoader\lib”下面的class文件】
2). 測試 + 自定義類加載器
(1). OuterClass測試代碼1 ----傳統方式
import userDefinedClasses.OuterClass;
public static void loadWithoutLoader(){
OuterClassouterClass =new OuterClass();
outerClass.showOuter();
}
main:
loadWithoutLoader();
[1]. 編譯正確
[2]. 運行拋出異常
【分析】
當前線程類加載器是AppClassLoader並採用委託機制去搜索指定的類,但是沒有找到OuterClass字節碼。所以拋出ClassNotFoundException異常
(2). OuterClass測試代碼2 ----反射方式
public static void loadWithoutLoaderII() throws ClassNotFoundException,InstantiationException, IllegalAccessException, NoSuchMethodException,SecurityException, IllegalArgumentException, InvocationTargetException{
Classclazz =Class.forName("userDefinedClasses.OuterClass");
Objectobj =clazz.newInstance();
Methodmethod =clazz.getMethod("showOuter", null);
method.invoke(obj,null);
}
[1]. 編譯同樣正確
[2]. 運行拋出異常
【分析】
採用Class.forName方法並傳入"userDefinedClasses.OuterClass"。但是由於當前線程的類加載器是AppClassLoader,所以還是沒有辦法根據這個字符串找到OuterClass的字節碼。所以拋出異常
(3). 解決辦法
由於系統的默認類加載器不能搜索到特定位置的類,所以必須重新自定義類加載器對這個指定目錄的類進行加載。通過loadClass方法加載之後會獲取這個OuterClass類的Class對象。這樣就可以通過反射來操作這個OuterClass類的對象
(4) 自定義類加載器加載指定目錄的字節碼文件
[1]. 自定義類加載器MyClassLoader
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
public class MyClassLoader extendsClassLoader {
@Override
protected Class<?> findClass(Stringname) throws ClassNotFoundException {
try {
FileInputStreamfis =new FileInputStream(new File(name +".class"));
byte[] classBytes =new byte[1024];
int length =fis.read(classBytes, 0, classBytes.length);
return defineClass(classBytes, 0,length);
}catch (FileNotFoundException e) {
e.printStackTrace();
}catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
[2]. 通過MyClassLoader加載D:\BlackHorse_Advanced\ClassLoader\lib下面的OuterClass
public static void loadWithMyClassLoader(StringclassName, ClassLoader classLoader) throws ClassNotFoundException,InstantiationException, IllegalAccessException, NoSuchMethodException,SecurityException, IllegalArgumentException, InvocationTargetException{
Classclazz =classLoader.loadClass("D:\\BlackHorse_Advanced\\ClassLoader\\lib\\"+ className);
Objectobj =clazz.newInstance();
Methodmethod =clazz.getMethod("showOuter", null);
method.invoke(obj,null);
}
main中:
ClassLoader myLoader =new MyClassLoader();
String className ="OuterClass";
loadWithMyClassLoader(className, myLoader);
[3]. 編譯通過,打印結果如下
Show Outer...
3). 總結
(1). 自定義類加載器需要顯式調用loadClass方法
由於自定義類加載器並不是系統類加載器的一部分,所以必須顯式調用loadClass才能加載指定的類。
【總結】無論是默認類加載器的隱式調用loadClass加載各自範圍的類
還是自定義類加載器的顯式調用loadClass加載指定位置的類,目的只有一個,將這個類的字節碼文件變成內存中的Class對象。
(2). 獲取Class類對象的第四種方式
自定義類加載器實例化之後,調用這個類加載器的loadClass方法。這樣就可以直接獲取這個指定位置【未必在默認類加載器的目錄下】的類。
----------- android培訓、java培訓、java學習型技術博客、期待與您交流! ------------