(轉)JVM——自定義類加載器
背景:為什麽要自定義,如何自定義,實現過程
轉載:http://blog.csdn.net/SEU_Calvin/article/details/52315125
0. 為什麽需要自定義類加載器
網上的大部分自定義類加載器文章,幾乎都是貼一段實現代碼,然後分析一兩句自定義ClassLoader的原理。但是我覺得首先得把為什麽需要自定義加載器這個問題搞清楚,因為如果不明白它的作用的情況下,還要去學習它顯然是很讓人困惑的。
首先介紹自定義類的應用場景:
(1)加密:Java代碼可以輕易的被反編譯,如果你需要把自己的代碼進行加密以防止反編譯,可以先將編譯後的代碼用某種加密算法加密,類加密後就不能再用Java的ClassLoader去加載類了,這時就需要自定義ClassLoader在加載類的時候先解密類,然後再加載
(2)從非標準的來源加載代碼:如果你的字節碼是放在數據庫、甚至是在雲端,就可以自定義類加載器,從指定的來源加載類。
(3)以上兩種情況在實際中的綜合運用:比如你的應用需要通過網絡來傳輸 Java 類的字節碼,為了安全性,這些字節碼經過了加密處理。這個時候你就需要自定義類加載器來從某個網絡地址上讀取加密後的字節代碼,接著進行解密和驗證,最後定義出在Java虛擬機中運行的類。
1. 雙親委派模型
在實現自己的ClassLoader之前,我們先了解一下系統是如何加載類的,那麽就不得不介紹雙親委派模型的實現過程。
//雙親委派模型的工作過程源碼 protected synchronizedClass<?> 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; }
ps:下面的總結,我覺得是最清晰的總結
雙親委派模型的工作過程如下:
(1)當前類加載器從自己已經加載的類中查詢是否此類已經加載,如果已經加載則直接返回原來已經加載的類。
(2)如果沒有找到,就去委托父類加載器去加載(如代碼c = parent.loadClass(name, false)所示)。父類加載器也會采用同樣的策略,查看自己已經加載過的類中是否包含這個類,有就返回,沒有就委托父類的父類去加載,一直到啟動類加載器。因為如果父加載器為空了,就代表使用啟動類加載器作為父加載器去加載。
(3)如果啟動類加載器加載失敗(例如在$JAVA_HOME/jre/lib裏未查找到該class),會使用拓展類加載器來嘗試加載,繼續失敗則會使用AppClassLoader來加載,繼續失敗則會拋出一個異常ClassNotFoundException,然後再調用當前加載器的findClass()方法進行加載。
雙親委派模型的好處:
(1)主要是為了安全性,避免用戶自己編寫的類動態替換 Java的一些核心類,比如 String。
(2)同時也避免了類的重復加載,因為 JVM中區分不同類,不僅僅是根據類名,相同的 class文件被不同的 ClassLoader加載就是不同的兩個類。
2. 自定義類加載器
(1)從上面源碼看出,調用loadClass時會先根據委派模型在父加載器中加載,如果加載失敗,則會調用當前加載器的findClass來完成加載。
(2)因此我們自定義的類加載器只需要繼承ClassLoader,並覆蓋findClass方法,下面是一個實際例子,在該例中我們用自定義的類加載器去加載我們事先準備好的class文件。
2.1 自定義一個People.java類做例子
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的內容總結完畢。
轉載請註明出處:http://blog.csdn.net/seu_calvin/article/details/52315125
(轉)JVM——自定義類加載器