一、自定義類加載器的一般步驟
Java的類加載器自從JDK1.2開始便引入了一條機制叫做父類委託機制。一個類需要被加載的時候,JVM先會調用他的父類加載器進行加載,父類調用父類的父類,一直到頂級類加載器。如果父類加載器加載不了,依次再使用其子類進行加載。當然這類所說的父類加載器,不一定他們之間是繼承的關係,有可能僅僅是包裝的關係。
Java之所以出現這條機制,因爲是處於安全性考慮。害怕用戶自己定義class文件然後自己寫一個類加載器來加載原本應該是JVM自己加載的類。這樣會是JVM虛擬機混亂或者說會影響到用戶的安全。下面我們來自己實現一個類加載器,其中主要就是繼承ClassLoader類。我們有必要明白:
雖然在絕大多數情況下系統默認提供的類加載器實現已經可以滿足需求。但是在某些情況下,您還是需要爲應用開發出自己的類加載器。比如您的應用通過網絡來傳輸 Java 類的字節代碼,爲了保證安全性,這些字節碼經過了加密處理。這個時候您就需要自己的類加載器來從某個網絡地址上讀取加密後的字節代碼,接着進行解密和驗證,最後定義出要在
Java 虛擬機中運行的類來。下面將通過兩個具體的實例來說明類加載器的開發。
①ClassLoader加載類的順序
1調用findLoadedClass(String) 來檢查是否已經加載類
2在父類加載器上調用loadClass方法。如果父親不能加載,一次一級一級傳給子類
3調用子類findClass(String) 方法查找類。若還加載不了就返回ClassNotFoundException,不交給發起請求的加載器的子加載器
②實現自己的類加載器
1 獲取類的class文件的字節數組,如loadClassData方法
2 將字節數組轉換爲Class類的實例,重寫findClass中調用的defineClass方法
- package cn.M_ClassLoader2;
-
- import java.io.ByteArrayOutputStream;
- import java.io.File;
- import java.io.FileInputStream;
-
-
- public class ClassLoaderTest
- {
- public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException
- {
-
- MyClassLoader cl = new MyClassLoader("myClassLoader");
-
-
- Class<?> clazz = cl.loadClass("cn.M_ClassLoader2.Animal");
-
-
- Animal animal = (Animal) clazz.newInstance();
- animal.say();
- }
- }
-
- class Animal
- {
- public void say()
- {
- System.out.println("hello world!");
- }
- }
-
- class MyClassLoader extends ClassLoader
- {
-
- private String name;
-
-
- private String path = MyClassLoader.getSystemClassLoader().getResource("").getPath();;
-
- MyClassLoader(String name)
- {
- this.name = name;
- }
-
- MyClassLoader(ClassLoader parent, String name)
- {
- super(parent);
- this.name = name;
- }
-
-
-
-
- @Override
- public Class<?> findClass(String name)
- {
- byte[] data = loadClassData(name);
- return this.defineClass(name, data, 0, data.length);
- }
-
- public byte[] loadClassData(String name)
- {
- try
- {
- name = name.replace(".", "//");
- FileInputStream is = new FileInputStream(new File(path + name + ".myclass"));
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- int b = 0;
- while ((b = is.read()) != -1)
- {
- baos.write(b);
- }
- System.out.println("我是自定義類加載器哦!");
- return baos.toByteArray();
- }
- catch (Exception e)
- {
- e.printStackTrace();
- }
- return null;
- }
- }
-
-
一般來說自己開發的類加載器只需要覆寫findClass(String name)
方法即可。java.lang.ClassLoader
類的方法loadClass()
封裝了前面提到的代理模式的實現。該方法會首先調用findLoadedClass()
方法來檢查該類是否已經被加載過;如果沒有加載過的話,會調用父類加載器的loadClass()
方法來嘗試加載該類;如果父類加載器無法加載該類的話,就調用findClass()
方法來查找該類。因此,爲了保證類加載器都正確實現代理模式,在開發自己的類加載器時,最好不要覆寫loadClass()
方法,而是覆寫findClass()
方法。
二、自定義類加載器的運行問題
由於只重寫了findClass方法並沒有重寫loadClass方法,故沒有改變父類委託機制。也就數說如果某個.class可以被父類加載,我們自定義的類加載器就不會被執行了。比如Animal.java被自動編譯爲Animal.class放在bin目錄下,AppClassLoader完全可以加載,所以就不調用自定義的加載器了。
嘗試辦法1:把Animal.class放在別的目錄中比如D盤的根目錄下
報 Class A can not access a member of class B with modifiers ""錯。Java語言中的包訪問成員實際上指的是運行時包訪問可見,而不是編譯時。因此當你試圖訪問不在同一個runtime package的成員時,即便在編譯時它們在同一個包內,但是卻由不同的class loader加載,也同樣會得到java.lang.IllegalAccessException:
Class A can not access a member of class B with modifiers "" 這樣的異常。
嘗試辦法2:把該Animal.class的後綴名爲.myClass,讓AppClassLoader找不到
網上有人說可以解決,但是我實驗的結果是會和辦法1報一樣的異常。
嘗試辦法3:解決方案是通過擴展自定義的ClassLoader,重寫loadClass方法先從當前類加載器加載再從父類加載器加載。
該解決辦法是可以解決的,網址是http://blog.csdn.net/zhangxinrun/article/details/6161426
參考博客
http://blog.csdn.net/zhangxinrun/article/details/6161426
http://blog.csdn.net/zhouysh/article/details/762300
http://blog.csdn.net/a352193394/article/details/7343385
http://blog.csdn.net/huangbiao86/article/details/6910152
http://www.cnblogs.com/feiling/archive/2012/08/29/2662909.html (加密字節碼)