ClassLoader類加載器
先看例子:
public class ClassLoaderTest{ public static void main(String[] args) { ClassLoader cl = ClassLoaderTest.class.getClassLoader(); System.out.println(cl); System.out.println(cl.getParent()); System.out.println(cl.getParent().getParent()); /** * List是rt.jar包下 */ System.out.println(List.class.getClassLoader()); } }
輸出結果:
[email protected]
[email protected]
null
null
系統默認三個類加載器
BootStrap(根加載器):java核心庫(rt.jar),C/C++寫,故而打印出 null
ExtClassLoader(擴展加載器):ext包下
AppClassLoader:classpath下
類加載器的委托機制:
當Java虛擬機要加載第一個類的時候,到底派出哪個類加載器去加載呢?
(1). 首先當前線程的類加載器去加載線程中的第一個類(當前線程的類加載器:Thread類中有一個get/setContextClassLoader(ClassLoader cl);方法,可以獲取/指定本線程中的類加載器)
(2). 如果類A中引用了類B,Java虛擬機將使用加載類A的類加載器來加載類B
(3). 還可以直接調用ClassLoader.loadClass(String className)方法來指定某個類加載器去加載某個類
每個類加載器加載類時,又先委托給其上級類加載器當所有祖宗類加載器沒有加載到類,回到發起者類加載器,還加載不了,則會拋出ClassNotFoundException,不是再去找發起者類加載器的兒子,因為沒有getChild()方法。例如:如上圖所示: MyClassLoader->AppClassLoader->ExtClassLoader->BootStrap.自定定義的MyClassLoader1首先會先委托給AppClassLoader,AppClassLoader會委托給ExtClassLoader,ExtClassLoader會委托給BootStrap,這時候BootStrap就去加載,如果加載成功,就結束了。如果加載失敗,就交給ExtClassLoader去加載,如果ExtClassLoader加載成功了,就結束了,如果加載失敗就交給AppClassLoader加載,如果加載成功,就結束了,如果加載失敗,就交給自定義的MyClassLoader1類加載器加載,如果加載失敗,就報ClassNotFoundException異常,結束。
在看一個例子 :
public class MyClassLoader extends ClassLoader{ protected MyClassLoader(ClassLoader parent) { super(parent); } protected MyClassLoader() { super(); } } public class ClassLoaderTest2 { public static void main(String[] args) { MyClassLoader ml1 = new MyClassLoader(); System.out.println(ml1.getParent()); MyClassLoader ml2 = new MyClassLoader(Thread.currentThread().getContextClassLoader().getParent()); System.out.println(ml2.getParent()); } }
打印結果:
[email protected]
[email protected]
說明自定義類加載器的父加載器默認是 :AppClassLoader
自定義的類加載器必須繼承抽象類ClassLoader
一般只需 重寫findClass方法,其實他內部還有一個loadClass方法和defineClass方法;
看loadClass源碼:
public Class<?> loadClass(String name) throws ClassNotFoundException { return loadClass(name, false); } protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { //同步處理 synchronized (getClassLoadingLock(name)) { // First, check if the class has already been loaded //檢查該類是否已經加載 Class c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { //如果有parent,就讓parent加載,如果找不到該類, //拋ClassNotFoundException ,讓子加載器加載,直到加載成功結束 if (parent != null) { c = parent.loadClass(name, false); } else { //如果自定義加載器parent為null,直接用根加載器加載 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. long t1 = System.nanoTime(); //自定義加載器重寫該方法 c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); } } if (resolve) { 解析class文件 resolveClass(c); } return c; } } //該方法直接拋出異常,就是為了重寫,所以自定義加載器只需要重寫findClass,如果重寫 // load方法,還需要重新寫容器的委托邏輯, 沒有必要 protected Class<?> findClass(String name) throws ClassNotFoundException { throw new ClassNotFoundException(name); }
defineClass這個方法很簡單就是將class文件的字節數組編程一個class對象,這個方法肯定不能重寫,內部實現是在C/C++代碼中實現的
自定義類加載器:
public class MyClassLoader extends ClassLoader{ private String base_dir; protected MyClassLoader(ClassLoader parent) { super(parent); } protected MyClassLoader() { super(); } public MyClassLoader(ClassLoader parent,String base_dir) { super(parent); this.base_dir = base_dir; } public MyClassLoader(String base_dir) { super(); this.base_dir = base_dir; } //重寫findClass @Override protected Class<?> findClass(String name) throws ClassNotFoundException { String path = this.base_dir+"\\"+name+".class"; byte[] b = new byte[0]; try { b = toByteArray(path); } catch (IOException e) { e.printStackTrace(); } if(b.length == 0){ return null; } //定義class信息,把class文件字節碼信息組裝成jvm的class信息 return defineClass(null,b,0,b.length); } private byte[] toByteArray(String filename) throws IOException { File f = new File(filename); if (!f.exists()) { throw new FileNotFoundException(filename); } ByteArrayOutputStream bos = new ByteArrayOutputStream((int) f.length()); BufferedInputStream in = null; try { in = new BufferedInputStream(new FileInputStream(f)); int buf_size = 1024; byte[] buffer = new byte[buf_size]; int len = 0; while (-1 != (len = in.read(buffer, 0, buf_size))) { bos.write(buffer, 0, len); } return bos.toByteArray(); } catch (IOException e) { e.printStackTrace(); throw e; } finally { try { in.close(); } catch (IOException e) { e.printStackTrace(); } bos.close(); } } } public class ClassLoaderTest3 { public static void main(String[] args) { MyClassLoader ml = new MyClassLoader("E:\\workspace\\mytest\\temp"); try { Class helloClass = ml.loadClass("Hello"); Object hello = helloClass.newInstance(); System.out.println(hello.getClass().getClassLoader()); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } }
打印結果:
[email protected]
這裏要聲明:很容易打印出 AppClassLoader,這是由於你本地idea創建的Hello.class文件可能存在 項目class目錄下,刪除,從其他目錄引入 即可;
現在來測試下自定義的類加載器:
public class Hello { public String sayHello(){ return "hello classLoader"; } } public class ClassLoaderTest3 { public static void main(String[] args) { //指定根路徑,可以根據實際情況引入 MyClassLoader ml = new MyClassLoader(ClassLoader.getSystemClassLoader().getParent(),"E:\\workspace\\mytest\\temp"); try { //加載 Class helloClass = ml.loadClass("Hello"); //初始化 Object hello = helloClass.newInstance(); //調用 加載類的方法 Object info = helloClass.getMethod("sayHello").invoke(hello); System.out.println(info); System.out.println(hello.getClass().getClassLoader()); } catch (Exception e) { e.printStackTrace(); } } }
打印結果:
hello classLoader
[email protected]
本文出自 “11898338” 博客,謝絕轉載!
ClassLoader類加載器