ClassLoader是用來動態的加載class文件到虛擬機中,並轉換成java.lang.class類的一個實例,每一個這樣的實例用來表示一個java類,咱們能夠根據Class的實例獲得該類的信息,並經過實例的newInstance()方法建立出該類的一個對象,除此以外,ClassLoader還負責加載Java應用所需的資源,如圖像文件和配置文件等。html
ClassLoader類是一個抽象類。若是給定類的二進制名稱,那麼類加載器會試圖查找或生成構成類定義的數據。通常策略是將名稱轉換爲某個文件名,而後從文件系統讀取該名稱的「類文件」。ClassLoader類使用委託模型來搜索類和資源。每一個 ClassLoader實例都有一個相關的父類加載器。須要查找類或資源時,ClassLoader實例會在試圖親自查找類或資源以前,將搜索類或資源的任務委託給其父類加載器。 java
注意:程序在啓動的時候,並不會一次性加載程序所要用的全部class文件,而是根據程序的須要,經過Java的類加載機制來動態加載某個class文件到內存中。數組
其體系結構圖以下:服務器
若是要實現本身的類加載器,不論是實現抽象列ClassLoader,仍是繼承URLClassLoader類,它的父加載器都是AppClassLoader,由於無論調用哪一個父類加載器,建立的對象都必須最終調用getSystemClassLoader()做爲父加載器,getSystemClassLoader()方法獲取到的正是AppClassLoader。網絡
注意:Bootstrap classLoader並不屬於JVM的等級層次,它不遵照ClassLoader的加載規則,Bootstrap classLoader並無子類。數據結構
JVM提供的類加載器,只能加載指定目錄的jar和class,若是咱們想加載其餘位置的類或jar時,例如加載網絡上的一個class文件,默認的ClassLoader就不能知足咱們的需求了,因此須要定義本身的類加載器。函數
咱們實現一個ClassLoader,並指定這個ClassLoader的加載路徑。有兩種方式:this
方式一:繼承ClassLoader,重寫父類的findClass()方法,代碼以下:spa
1 import java.io.ByteArrayOutputStream; 2 import java.io.File; 3 import java.io.FileInputStream; 4 import java.io.IOException; 5 public class PathClassLoader extends ClassLoader 6 { 7 public static final String drive = "d:/"; 8 public static final String fileType = ".class"; 9 10 public static void main(String[] args) throws Exception 11 { 12 PathClassLoader loader = new PathClassLoader(); 13 Class<?> objClass = loader.loadClass("HelloWorld", true); 14 Object obj = objClass.newInstance(); 15 System.out.println(objClass.getName()); 16 System.out.println(objClass.getClassLoader()); 17 System.out.println(obj.getClass().toString()); 18 } 19 20 public Class<?> findClass(String name) 21 { 22 byte[] data = loadClassData(name); 23 return defineClass(name, data, 0, data.length);// 將一個 byte 數組轉換爲 Class// 類的實例 24 } 25 public byte[] loadClassData(String name) 26 { 27 FileInputStream fis = null; 28 byte[] data = null; 29 try 30 { 31 fis = new FileInputStream(new File(drive + name + fileType)); 32 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 33 int ch = 0; 34 while ((ch = fis.read()) != -1) 35 { 36 baos.write(ch); 37 } 38 data = baos.toByteArray(); 39 } catch (IOException e) 40 { 41 e.printStackTrace(); 42 } 43 return data; 44 } 45 }
在第13行,咱們調用了父類的loadClass()方法,該方法使用指定的二進制名稱來加載類,下面是loadClass方法的源代碼:code
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // 第一步先檢查這個類是否已經被加載 Class<?> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { //parent爲父加載器 if (parent != null) { //將搜索類或資源的任務委託給其父類加載器 c = parent.loadClass(name, false); } else { //檢查該class是否被BootstrapClassLoader加載 c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } if (c == null) { //若是上述兩步均沒有找到加載的class,則調用findClass()方法 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) { resolveClass(c); } return c; } }
這個方法首先檢查指定class是否已經被加載,若是已被加載過,則調用resolveClass()方法連接指定的類,若是還未加載,則先將搜索類或資源的任務委託給其父類加載器,檢查該class是否被BootstrapClassLoader加載,若是上述兩步均沒有找到加載的class,則調用findClass()方法,在咱們自定義的加載器中,咱們重寫了findClass方法,去咱們指定的路徑下加載class文件。
另外,咱們自定義的類加載器沒有指定父加載器,在JVM規範中不指定父類加載器的狀況下,默認採用系統類加載器即AppClassLoader做爲其父加載器,因此在使用該自定義類加載器時,須要加載的類不能在類路徑中,不然的話根據雙親委派模型的原則,待加載的類會由系統類加載器加載。若是必定想要把自定義加載器須要加載的類放在類路徑中, 就要把自定義類加載器的父加載器設置爲null。
方式二:繼承URLClassLoader類,而後設置自定義路徑的URL來加載URL下的類。
咱們將指定的目錄轉換爲URL路徑,而後重寫findClass方法。
所謂熱部署,就是在應用正在運行的時候升級軟件,不須要從新啓用應用。
對於Java應用程序來講,熱部署就是運行時更新Java類文件。在基於Java的應用服務器實現熱部署的過程當中,類裝入器扮演着重要的角色。大多數基於Java的應用服務器,包括EJB服務器和Servlet容器,都支持熱部署。
類裝入器不能從新裝入一個已經裝入的類,但只要使用一個新的類裝入器實例,就能夠將類再次裝入一個正在運行的應用程序。
前面的分析,咱們已經知道,JVM在加載類以前會檢查請求的類是否已經被加載過來,也就是要調用findLoadedClass方法查看是否可以返回類實例。若是類已經加載過來,再調用loadClass會致使類衝突。
可是,JVM判斷一個類是不是同一個類有兩個條件:一是看這個類的完整類名是否同樣(包括包名),二是看加載這個類的ClassLoader加載器是不是同一個(既是是同一個ClassLoader類的兩個實例,加載同一個類也會不同)。
因此,要實現類的熱部署能夠建立不一樣的ClassLoader的實例對象,而後經過這個不一樣的實例對象來加載同名的類。