背景:據說ClassLoader類加載機制是進入BAT的必經之路。java
ClassLoader總述:緩存
普通的Java開發其實用到ClassLoader的地方並很少,可是理解透徹ClassLoader類的加載機制,不管是對咱們編寫更高效的代碼仍是進BAT都大有裨益;而從「黃埔軍校」出來的我對ClassLoader的理解都是借鑑了不少書籍和博客,站在了各大博主的肩膀上,感謝大家!上菜,Classloader最主要的做用就是將Java字節碼文件(後綴爲.class)加載到JVM中,JVM在啓動時不會一次性加載全部的class文件,而是根據須要動態加載class文件,畢竟一次性加載太多jar包的class文件JVM吃不消;下面主要研究Bootstrap ClassLoader、Extention ClassLoader和AppClassLoader這三種類加載器。tomcat
談到ClassLoader就想到咱們安裝JDK的時候都會在控制檯輸入java、javac驗證是否安裝成功,而這個javac就是Java ClassLoader,測試是否能把Java源文件正確編譯成Java字節碼文件,下面的截圖就是個javac的小例子,javac以後加載器把Java源文件編譯成TestClassLoader.class字節碼文件。安全
因爲下面要講到ClassLoader的加載路徑,這裏順便把Java的環境變量也複習一遍。網絡
JAVA_HOME:app
指的是安裝JDK的位置,如:JAVA_HOME="/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home" 。ide
PATH:測試
配置PATH(程序的路徑)的做用將就是可以在命令行窗口直接鍵入程序的名字了,而再也不須要鍵入它的全路徑,好比上面代碼中我用的到javac
和java
兩個命令。如:PATH=".$PATH:$JAVA_HOME/bin" ;就是在JAVA_HOME路徑上添加了JDK下的bin目錄便可。
加密
CLASSPATH:spa
CLASSPATH就是指向jar包的路徑,如:PATH=".$PATH:$JAVA_HOME/bin" ;"."表示當前目錄。
ClassLoader類加載流程:
三個Class Loader的執行順序是:Bootstrap CLassloder -> Extention ClassLoader -> AppClassLoader;
一、Bootstrap CLassloder是最頂層的加載類,主要是加載核心類庫,也就是%JRE_HOME%\lib下的rt.jar、resources.jar、charsets.jar和class等資源;而且,能夠經過啓動JVM時指定-Xbootclasspath和路徑來改變Bootstrap ClassLoader的加載目錄,下面有個小荔子。
二、Extention ClassLoader是擴展的類加載器,其加載的是目錄%JRE_HOME%\lib\ext目錄下的jar包和class文件;它一樣也能夠加載-D java.ext.dirs選項指定的目錄。
三、Appclass Loader是用於加載當前應用的classpath的全部類,其也稱爲SystemAppClass。
另外有興趣的還能夠看下Launcher
類的源碼,源碼中規定了三個加載器的環境屬性分別爲B:sun.boot.class.path、E:java.ext.dirs和A:java.class.path;下面經過代碼來簡單測試寫,如圖:
打印輸出結果:
BootstrapClassLoader:
/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/resources.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/rt.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/sunrsasign.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/jsse.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/jce.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/charsets.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/jfr.jar:
/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/classes
ExtClassLoader:
/Users/apple/Library/Java/Extensions:
/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/ext:
/Library/Java/Extensions:/Network/Library/Java/Extensions:
/System/Library/Java/Extensions:/usr/lib/java
AppClassLoader:
/TJT/Eclipse/workspace/tjt/bin:
/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home/jre/lib/rt.jar
爲了更好的理解三者之間加載的關係,咱們來測試一個類的加載器和它的父類加載以及一些不是咱們建立的類如String、Double、int等基礎類:
從上圖中可用看出,本身編寫的類Test2.class文件是由AppClassLoader加載的,而且AppClassLoader有父加載器ExtClassLoader,但ExtClassLoader的父加載器爲null;Double.class這個Java基礎類的加載器爲null,其父加載也爲空且程序還會報空指針異常錯誤;其實呢,Double.class是有Bootstrap CLassLoader加載的,也並非每一個加載器都有父加載器;總的來講就是JVM啓動時經過Bootstrap類加載器加載rt.jar等核心jar包中的class文件,諸如一些int.class,String.class都是由它加載;JVM初始化sun.misc.Launcher並建立Extension ClassLoader和AppClassLoader實例,且將ExtClassLoader設置爲AppClassLoader的父加載器;而Bootstrap雖然沒有父加載器,可是它卻能夠做爲一個ClassLoader的父加載器;另外,一個ClassLoader建立時若是沒有指定parent,那麼它的parent默認就是AppClassLoader;
雙親委託:
當一個類加載器查找class和resource時,是經過「委託模式」進行的,它首先會判斷這個class是否是已經加載成功,若是沒有加載的話它並非本身進行查找,而是先經過父加載器,而後遞歸下去,直到Bootstrap ClassLoader,若是Bootstrap classloader找到了,直接返回,若是沒有找到,則一級一級返回,最後是由自身去查找這些對象;這種機制就叫作雙親委託。
從上圖可用看出ClassLoader的加載序列,委託是從下往上,查找過程則是從上向下的,如下有幾個注意事項:
一、一個AppClassLoader查找資源時,首先會查看緩存是否有,如有則從緩存中獲取,不然委託給父加載器;
2.、重複第一步的遞歸操做,查詢類是否已被加載;
3.、若是ExtClassLoader也沒有加載過,則由Bootstrap ClassLoader加載,它首先也會查找緩存,若是沒有找到的話,就去找本身的規定的路徑下,也就是sun.mic.boot.class下面的路徑,找到就返回,找不到就讓子加載器本身去找。
4.、Bootstrap ClassLoader若是沒有查找成功,則ExtClassLoader本身在java.ext.dirs路徑中去查找,查找成功就返回,查找不成功則再向下讓子加載器找。
5.、如果ExtClassLoader查找不成功,則由ppClassLoader在java.class.path路徑下本身查找查找,找到就返回,若是沒有找到就讓子類找,若是沒有子類則會拋出各類異常。
自定義CLassLoader:
在ClassLoader中有四個很重要實用的方法loadClass()、findLoadedClass()、findClass()、defineClass(),能夠用來建立屬於本身的類的加載方式;好比咱們須要動態加載一些東西,或者從C盤某個特定的文件夾加載一個class文件,又或者從網絡上下載class主內容而後再進行加載等。分三步搞定:
一、編寫一個類繼承ClassLoader抽象類;
二、重寫findClass()方法;
三、在findClass()方法中調用defineClass()方法便可實現自定義ClassLoader;
需求:自定義一個classloader其默認加載路徑爲"/TJT/Code"下的jar包和資源。首先建立一個Test.java,而後javac編譯並把生成的Test.class文件放到"/TJT/Code"路徑下,而後再編寫一個DiskClassLoader繼承ClassLoader,最後經過
FindClassLoader的測試類,調用再Test.class裏面的一個find()方法。
1 package www.baidu; 2 import java.io.ByteArrayOutputStream; 3 import java.io.File; 4 import java.io.FileInputStream; 5 import java.io.IOException; 6
7 public class DiskClassLoader extends ClassLoader{ 8 //自定義classLoader能將class二進制內容轉換成Class對象
9 private String myPath; 10
11 public DiskClassLoader(String path) { 12 myPath = path; 13 } 14
15 //findClass()方法中定義了查找class的方法
16 @Override 17 protected Class<?> findClass(String name) throws ClassNotFoundException{ 18 String fileName = getFileName(name); 19 File file = new File(myPath,fileName); 20 try { 21 FileInputStream is = new FileInputStream(file); 22 ByteArrayOutputStream bos = new ByteArrayOutputStream(); 23 int len = 0; 24 try { 25 while((len = is.read()) != -1) { 26 bos.write(len); 27 } 28 } catch (IOException e) { 29 e.printStackTrace(); 30 } 31 byte[] data = bos.toByteArray(); 32 is.close(); 33 bos.close(); 34 //數據經過defineClass()生成了Class對象
35 return defineClass(name, data,0,data.length ); 36 } catch (Exception e) { 37 e.printStackTrace(); 38 } 39 return super.findClass(name); 40 } 41
42 private String getFileName(String name) { 43 int lastIndexOf = name.lastIndexOf('.'); 44 if (lastIndexOf == -1) { 45 return name + ".class"; 46 }else { 47 return name.substring(lastIndexOf + 1) + ".class"; 48 } 49 } 50 }
測試結果以下:找到了自定義的加載路徑而且調用了類中的find()方法
1 package www.baidu; 2 import java.lang.reflect.Method; 3
4 public class FindClassLoader { 5 public static void main(String[] args) throws ClassNotFoundException { 6 //建立自定義classloader對象
7 DiskClassLoader diskL = new DiskClassLoader("/TJT/Code"); 8 System.out.println("classloader is: "+diskL); 9 try { 10 //加載class文件
11 Class clazz = diskL.loadClass("www.baidu.Test"); 12 if (clazz != null) { 13 Object object = clazz.newInstance(); 14 Method declaredMethod = clazz.getDeclaredMethod("find", null); 15 //經過反射調用Test類的find()方法
16 declaredMethod.invoke(object, null); 17 } 18 } catch (Exception e) { 19 e.printStackTrace(); 20 } 21 } 22 }
總結:
除此以外,ClassLoader還能夠進行程序加密(好比你寫了比較騷的jar包),這樣咱們就能夠在程序中加載特定了類,而且這個類只能被咱們自定義的加載器進行加載,提升了程序的安全性,可是用的很少;反正咱們在項目上是不容許用ClassLoader加密,寧願裸奔,瞭解一下。另外就是tomcat的類加載機制也是遵循雙親委派機制的,而且大部分的加載機制和JVM類加載機制同樣,理解了Bootstrap ClassLoader、Extention ClassLoader和AppClassLoader這三種加載器後再看tomcat類的加載就能夠橫着走了。