java類載入器,java類載入
本篇文章主要介紹java類載入器,以及自己動手編寫自己的簡單的類載入器。
java虛擬機器中可以安裝多個類載入器,預設的類載入器有3個:BootStrap,ExtClassLoader,AppClassLoader。不同的 類記載負責載入不同位置的類。類載入器本身也是java類,因為類載入器本身也要被類載入器載入,顯然必須要有第一個類載入器,這個類載入器不是java類,這個類載入器就是BootStrap。類載入器是以父子的樹形關係進行組織的,在執行個體化每個類載入器時,需要為其指定一個父類載入器或者採用系統類別載入器作為其父類載入器。列出了類載入器的關係以及其管轄範圍:
下面寫代碼來測試下如上的關係:
ClassLoader loader = ClassLoaderDemo.class.getClassLoader();while( loader != null){System.out.println(loader.getClass().getName());loader = loader.getParent();}運行上面的代碼得出如下的結果:
sun.misc.Launcher$AppClassLoader
sun.misc.Launcher$ExtClassLoader
從上面的結果可以分析出:最頂層的類載入器擷取到的是null,為什麼是null呢?因為這個類載入器不是java類,而是用c寫的。從而驗證了如上的關係。
另外介紹一個類載入器的委任機制,比如要載入某個類時,子類載入器會詢問父類載入器是載入了,直接追溯到在高層的類載入器,如果父類載入器都不能載入了,則發起源類載入器再去載入,再載入不到就會跑出ClassNotFoundException異常,而不會再去找子類載入器去載入。可以去做實驗去驗證這點,比如將當前類路徑下面的某個類(這個類主要輸出載入這個類的類載入器)打成jar放到jre/lib/ext目錄下,運行查看輸出結果。為什麼會有類載入器的委任機制呢?主要是出於安全性的考慮,使用者可以自訂類載入器,加入沒有類載入器的委任機制就很難確保記憶體中只有某個類的一份位元組碼,假如委任給父類載入器處理,只有父類載入器載入過了或者能載入就交給父載入器。
當java虛擬機器要載入一個類時,到底派誰去載入呢?
- 首先當前線程的類載入器去載入線程的第一個類;
- 如果A類引用了B類,Java虛擬機器將使用載入A類的類載入器去載入B類
- 也可以直接使用ClassLoader.loadClass()方法來指定某個類載入器去載入某個類
java中是怎樣載入類的呢?在java中引進類載入器,用來載入類的,相應的類為ClassLoader,此類為抽象類別,自訂的類載入器需要繼承此類,從api中,可以看到,類載入器載入類時使用的方法為ClassLoader,此方法有相應的去載入類的一些演算法,自訂類載入器需要實現public Class findClass(String name)方法,參數為類名,返回一個Class對象,ClassLoader內部實現了一個defineClass方法,這個類根據你提供的類的位元組數組來載入類,ClassLoader使用了模板方法設計模式。
下面給出自訂類載入器的代碼,先通過MyClassLoader的main方法來對某個編譯後的類進行加密,然後類載入器載入時再進行解密,加解密演算法很簡單,只用到了異或
package cn.zq.demo;import java.io.ByteArrayOutputStream;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.InputStream;import java.io.OutputStream;public class MyClassLoader extends ClassLoader{/** * class檔案的副檔名 */private static final String CLASS_EXT = ".class";/** * 類所在的路徑. */private String path;public MyClassLoader(String path) {super();this.path = path;}/** * 此方法由jvm調用 */public Class<?> findClass(String name) throws ClassNotFoundException {byte[] b = loadClassData(name);return defineClass(name, b, 0, b.length);}/** * 讀取class檔案 * @param name 類的完整名稱 * @return 二進位位元組數組 */private byte[] loadClassData(String name) {FileInputStream fis = null;try {name = name.substring(name.lastIndexOf(".") + 1);fis = new FileInputStream(path + "\\" + name + CLASS_EXT);ByteArrayOutputStream baos = new ByteArrayOutputStream();cypher(fis, baos);return baos.toByteArray();} catch (Exception e) {throw new RuntimeException(e);}finally{if(fis != null){try {fis.close();} catch (Exception e) {throw new RuntimeException(e);}finally{fis = null;}}}}private static void cypher(InputStream in, OutputStream out) throws Exception{int b = -1;while( ( b = in.read() ) != -1){out.write(b ^ 0XFF);}}public static void main(String[] args) throws Exception{String srcPath = args[0]; //源class檔案所在路徑String fileDir = args[1]; //加密後的class檔案所在目錄FileInputStream fis = new FileInputStream(srcPath);String filename = srcPath.substring(srcPath.lastIndexOf("\\") + 1);String destPath = fileDir + "\\" + filename;FileOutputStream fos = new FileOutputStream(destPath);cypher(fis, fos);fos.close();fis.close();}}
package cn.zq.demo;public class Data {public String toString() {return "My data!";}}
package cn.zq.demo;public class ClassLoaderDemo {public static void main(String[] args) throws Exception {ClassLoader loader = ClassLoaderDemo.class.getClassLoader();while( loader != null){System.out.println(loader.getClass().getName());loader = loader.getParent();} MyClassLoader myClassLoader = new MyClassLoader("classfile");Object o = myClassLoader.loadClass("cn.zq.demo.Data").newInstance();System.out.println(o.getClass().getName() + "->" + o);}}
運行上面的代碼有可能出現的異常為:
java.lang.ClassFormatError: Incompatible magic value 889275713 in class file cn/zq/demo/Data
表示類檔案是無效的,
運行上面的程式如果出現的結果是:cn.zq.demo.Data->My data!
那麼恭喜,您的程式可以跑通了,你的類載入器成功運行了,最後給出專案檔的地址,大家需要可以自行下載,如果遇到什麼問題可以及時的聯絡我,隨時為您解答各種力所能及的問題。
原始碼地址