黑馬程序員--05.類加載器--05【自定義類加載器】【自定義類加載器舉例】

類加載器--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. 調用ClassLoaderdefineClass(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學習型技術博客、期待與您交流! ------------

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章