(問:自定義類加載器怎么實現,其中哪個方法走雙親委派模型,(實現findclass方法,一般用defineclass加載外部類),如何才能不走雙親委派。(重寫loadclass方法))
三個重要函數:loadClass,findClass,defineClass
loadClass:調用父類加載器的loadClass,加載失敗則調用自己的findClass方法
findClass:根據名稱讀取文件存入字節數組
defineClass:把一個字節數組轉為Class對象
0. 為什么需要自定義類加載器
網上的大部分自定義類加載器文章,幾乎都是貼一段實現代碼,然后分析一兩句自定義ClassLoader的原理。但是我覺得首先得把為什么需要自定義加載器這個問題搞清楚,因為如果不明白它的作用的情況下,還要去學習它顯然是很讓人困惑的。
首先介紹自定義類的應用場景:
(1)加密:Java代碼可以輕易的被反編譯,如果你需要把自己的代碼進行加密以防止反編譯,可以先將編譯后的代碼用某種加密算法加密,類加密后就不能再用Java的ClassLoader去加載類了,這時就需要自定義ClassLoader在加載類的時候先解密類,然后再加載。
(2)從非標准的來源加載代碼:如果你的字節碼是放在數據庫、甚至是在雲端,就可以自定義類加載器,從指定的來源加載類。
(3)以上兩種情況在實際中的綜合運用:比如你的應用需要通過網絡來傳輸 Java 類的字節碼,為了安全性,這些字節碼經過了加密處理。這個時候你就需要自定義類加載器來從某個網絡地址上讀取加密后的字節代碼,接着進行解密和驗證,最后定義出在Java虛擬機中運行的類。
1. 雙親委派模型
在實現自己的ClassLoader之前,我們先了解一下系統是如何加載類的,那么就不得不介紹雙親委派模型的實現過程。
//雙親委派模型的工作過程源碼 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) { c = parent.loadClass(name, false); } else { c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader //父類加載器無法完成類加載請求 } 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; }
雙親委派模型的工作過程如下:
(1)當前類加載器從自己已經加載的類中查詢是否此類已經加載,如果已經加載則直接返回原來已經加載的類。
(2)如果沒有找到,就去委托父類加載器去加載(如代碼c = parent.loadClass(name, false)所示)。父類加載器也會采用同樣的策略,查看自己已經加載過的類中是否包含這個類,有就返回,沒有就委托父類的父類去加載,一直到啟動類加載器。因為如果父加載器為空了,就代表使用啟動類加載器作為父加載器去加載。
(3)如果啟動類加載器加載失敗(例如在$JAVA_HOME/jre/lib里未查找到該class),會使用拓展類加載器來嘗試加載,繼續失敗則會使用AppClassLoader來加載,繼續失敗則會拋出一個異常ClassNotFoundException,然后再調用當前加載器的findClass()方法進行加載。
比如要加載自己寫的String類,自定義一個String類放在某路徑下,自定義一個類加載器繼承ClassLoader類,並實現findClass方法(在自己的路徑下去取String類)。重寫loadClass方法讓它不走雙親委派,這樣他就會直接調用findClass加載自己的String類了。
雙親委派模型的好處:
(1)主要是為了安全性,避免用戶自己編寫的類動態替換 Java的一些核心類,比如 String。
(2)同時也避免了類的重復加載,因為 JVM中區分不同類,不僅僅是根據類名,相同的 class文件被不同的 ClassLoader加載就是不同的兩個類。
2. 自定義類加載器
(1)從上面源碼看出,調用loadClass時會先根據委派模型在父加載器中加載,如果加載失敗,則會調用當前加載器的findClass來完成加載。
(2)因此我們自定義的類加載器只需要繼承ClassLoader,並覆蓋findClass方法,下面是一個實際例子,在該例中我們用自定義的類加載器去加載我們事先准備好的class文件。
2.1 自定義一個People.java類做例子,.java編譯后生成.class,即二進制字節流文件
public class People { //該類寫在記事本里,在用javac命令行編譯成class文件,放在d盤根目錄下 private String name; public People() {} public People(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String toString() { return "I am a people, my name is " + name; } }
2.2 自定義類加載器
自定義一個類加載器,需要繼承ClassLoader類,並實現findClass方法。其中defineClass方法可以把二進制流字節組成的文件轉換為一個java.lang.Class(只要二進制字節流的內容符合Class文件規范)。
import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.nio.ByteBuffer; import java.nio.channels.Channels; import java.nio.channels.FileChannel; import java.nio.channels.WritableByteChannel; public class MyClassLoader extends ClassLoader { public MyClassLoader() { } public MyClassLoader(ClassLoader parent) { super(parent); } protected Class<?> findClass(String name) throws ClassNotFoundException { File file = new File("D:/People.class"); try{ byte[] bytes = getClassBytes(file);
//defineClass方法可以把二進制流字節組成的文件轉換為一個java.lang.Class Class<?> c = this.defineClass(name, bytes, 0, bytes.length); return c; } catch (Exception e) { e.printStackTrace(); } return super.findClass(name); } private byte[] getClassBytes(File file) throws Exception { // 這里要讀入.class的字節,因此要使用字節流 FileInputStream fis = new FileInputStream(file); FileChannel fc = fis.getChannel(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); WritableByteChannel wbc = Channels.newChannel(baos); ByteBuffer by = ByteBuffer.allocate(1024); while (true){ int i = fc.read(by); if (i == 0 || i == -1) break; by.flip(); wbc.write(by); by.clear(); } fis.close(); return baos.toByteArray(); } }
2.3 在主函數里使用
MyClassLoader mcl = new MyClassLoader(); Class<?> clazz = Class.forName("People", true, mcl); Object obj = clazz.newInstance(); System.out.println(obj); System.out.println(obj.getClass().getClassLoader());//打印出我們的自定義類加載器
2.4 運行結果
ClassLoader中的defineClass方法:
//Converts an array of bytes into an instance of class <tt>Class</tt>. protected final Class<?> defineClass(String name, byte[] b, int off, int len) throws ClassFormatError { return defineClass(name, b, off, len, null); }
另一個自定義類加載器示例
首先,我們定義一個待加載的普通Java
類:Test.java
。放在com.huachao.cl
包下:
package com.huachao.cl; public class Test { public void hello() { System.out.println("恩,是的,我是由 " + getClass().getClassLoader().getClass() + " 加載進來的"); } }
注意:
如果你是直接在當前項目里面創建,待
Test.java
編譯后,請把Test.class
文件拷貝走,再將Test.java
刪除。因為如果Test.class
存放在當前項目中,根據雙親委派模型可知,會通過sun.misc.Launcher$AppClassLoader
類加載器加載。為了讓我們自定義的類加載器加載,我們把Test.class文件放入到其他目錄。
在本例中,我們Test.class文件存放的目錄如下:

接下來就是自定義我們的類加載器:
import java.io.FileInputStream; import java.lang.reflect.Method; public class Main { static class MyClassLoader extends ClassLoader { private String classPath; public MyClassLoader(String classPath) { this.classPath = classPath; } private byte[] loadByte(String name) throws Exception { name = name.replaceAll("\\.", "/"); FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class"); int len = fis.available(); byte[] data = new byte[len]; fis.read(data); fis.close(); return data; } protected Class<?> findClass(String name) throws ClassNotFoundException { try { byte[] data = loadByte(name); return defineClass(name, data, 0, data.length); } catch (Exception e) { e.printStackTrace(); throw new ClassNotFoundException(); } } }; public static void main(String args[]) throws Exception { MyClassLoader classLoader = new MyClassLoader("D:/test"); Class clazz = classLoader.loadClass("com.huachao.cl.Test"); Object obj = clazz.newInstance(); Method helloMethod = clazz.getDeclaredMethod("hello", null); helloMethod.invoke(obj, null); } }
轉載請注明出處:http://blog.csdn.net/seu_calvin/article/details/52315125