Java類加載器學習2——自定義類加載器和父類委托機製帶來的問題
一、自定義類加載器的一般步驟
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對象 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; } /** * 重寫findClass方法 */ @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方法先從當前類加載器加載再從父類加載器加載。
該解決辦法是可以解決的,網址是https://blog.csdn.net/zhangxinrun/article/details/6161426
參考博客
https://blog.csdn.net/zhangxinrun/article/details/6161426
https://blog.csdn.net/zhouysh/article/details/762300
https://blog.csdn.net/a352193394/article/details/7343385
https://blog.csdn.net/huangbiao86/article/details/6910152
https://www.cnblogs.com/feiling/archive/2012/08/29/2662909.html (加密字節碼)
最後更新:2017-04-03 20:19:47