背景:聽說ClassLoader類載入機制是進入BAT的必經之路。 ClassLoader總述: 普通的Java開發其實用到ClassLoader的地方並不多,但是理解透徹ClassLoader類的載入機制,無論是對我們編寫更高效的代碼還是進BAT都大有裨益;而從“黃埔軍校”出來的我對ClassLo ...
背景:聽說ClassLoader類載入機制是進入BAT的必經之路。
ClassLoader總述:
普通的Java開發其實用到ClassLoader的地方並不多,但是理解透徹ClassLoader類的載入機制,無論是對我們編寫更高效的代碼還是進BAT都大有裨益;而從“黃埔軍校”出來的我對ClassLoader的理解都是借鑒了很多書籍和博客,站在了各大博主的肩膀上,感謝你們!上菜,Classloader最主要的作用就是將Java位元組碼文件(尾碼為.class)載入到JVM中,JVM在啟動時不會一次性載入所有的class文件,而是根據需要動態載入class文件,畢竟一次性載入太多jar包的class文件JVM吃不消;下麵主要研究Bootstrap ClassLoader、Extention ClassLoader和AppClassLoader這三種類載入器。
談到ClassLoader就想到我們安裝JDK的時候都會在控制台輸入java、javac驗證是否安裝成功,而這個javac就是Java ClassLoader,測試是否能把Java源文件正確編譯成Java位元組碼文件,下麵的截圖就是個javac的小例子,javac之後載入器把Java源文件編譯成TestClassLoader.class位元組碼文件。
由於下麵要講到ClassLoader的載入路徑,這裡順便把Java的環境變數也複習一遍。
JAVA_HOME:
指的是安裝JDK的位置,如:JAVA_HOME="/Library/Java/JavaVirtualMachines/jdk1.8.0_191.jdk/Contents/Home" 。
PATH:
配置PATH(程式的路徑)的作用將就是能夠在命令行視窗直接鍵入程式的名字了,而不再需要鍵入它的全路徑,比如上面代碼中我用的到javac
和java
兩個命令。如:PATH=".$PATH:$JAVA_HOME/bin" ;就是在JAVA_HOME路徑上添加了JDK下的bin目錄即可。
CLASSPATH:
CLASSPATH就是指向jar包的路徑,如:PATH=".$PATH:$JAVA_HOME/bin" ;"."表示當前目錄。
ClassLoader類載入流程:
三個Class Loader的執行順序是:Bootstrap CLassloder -> Extention ClassLoader -> AppClassLoader;
1、Bootstrap CLassloder是最頂層的載入類,主要是載入核心類庫,也就是%JRE_HOME%\lib下的rt.jar、resources.jar、charsets.jar和class等資源;並且,可以通過啟動JVM時指定-Xbootclasspath和路徑來改變Bootstrap ClassLoader的載入目錄,下麵有個小荔子。
2、Extention ClassLoader是擴展的類載入器,其載入的是目錄%JRE_HOME%\lib\ext目錄下的jar包和class文件;它同樣也可以載入-D java.ext.dirs選項指定的目錄。
3、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的載入序列,委托是從下往上,查找過程則是從上向下的,以下有幾個註意事項:
1、一個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主內容然後再進行載入等。分三步搞定:
1、編寫一個類繼承ClassLoader抽象類;
2、重寫findClass()方法;
3、在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類的載入就可以橫著走了。