加載器理解
一、類加載器
·簡要介紹什麼是類加載器和類加載器的作用。
簡單說,類加載器就是加載類的工具。當出現一個類,要用到此類的時候,Java虛擬機首先將類字節碼加載進內存,通常字節碼的原始信息放在硬盤上的classpath指定目錄下。
類加載器的作用:將.class文件內容加載進內存進行處理,處理完後的結果就是字節碼。
·Java虛擬機可以安裝多個類加載器,系統默認三個主要的類加載器,每個類加載器負責加載特定位置的類:
BootStrap,ExtClassLoader,AppClassLoader。
·BootStrap--頂級類加載器:
類加載器本身也是Java類,因爲它是Java類,本身也需要加載器加載,顯然必須有第一個類加載器而不是java類的,這正是BootStrap。它是嵌套在Java虛擬機內核中的,已啓動
即出現在虛擬機中,是用c++寫的一段二進制代碼。所以不能通過java程序獲取其名字,獲得的只能是null。
·Java虛擬機中的所有類加載器採用具有父子關係的樹形結構進行組織,在實例化每個類加載器對象時,需要爲其制定一個父級類加載器對象或者採用默認系統類加載器作爲其父級
類加載器。
二、類加載器的委託機制
·當Java虛擬機要加載一個類時,到底派出哪個類加載器去加載呢?
1)首先當前線程的類加載器去加載線程中的第一個類。
2)如果類中引用了類B,Java虛擬機將使用類加載器A的類加載器來加載類B。
3)還可以直接調用ClassLoader.loadClass()方法類指定某個類加載器去加載某個類。
·每個類加載器加載類時,又先委託給其上級類加載器。
1)當所有祖宗類加載器沒有加載到類,回到發起者類加載器,還加載不了,則拋ClassNotFoundException,不是再去找發起者類加載器的兒子,因爲沒有getChild方法,即使有,哪有多個兒子,找哪一個呢?
2)對着類加載器的層次結構圖和委託加載原理,解釋先前將ClassLoaderTest輸出成jre/lib/ext目錄下的itcast.jar包中後,運行結果爲ExtClassLoader的原因。
每個ClassLoader本身只能分別加載特定位置和目錄中的類,但它們可以委託其他的類加載器去加載類,這就是類加載器的委託模式。類加載器一級級委託到BootStrap類加載
器,當BootStrap無法加載當前所要加載的類時,然後才一級級回退到子孫類加載器去進行真正的加載。當回退到最初的類加載器時,如果它自己也不能完成類的加載,那就應
報告ClassNotFoundException異常。
有一道面試,能不能自己寫個類叫java.lang.System,爲了不讓我們寫System類,類加載採用委託機制,這樣可以保證爸爸們優先,也就是總是使用爸爸們能找到的類,這樣
總是使用java系統提供的System。
把先前編寫的類加入到jdk的rt.jar中,會有怎樣的效果呢?不行!!!看來是不能隨意將自己的class文件加入進rt.jar文件中的。
三、編寫自己的類加載器
知識講解:
1)自定義的類加載器的必須繼承ClassLoader
loadClass方法(直接繼承,省去委託機制的編寫)與findClass方法(覆蓋這個就行了)
defineClass方法
2)編程步驟:
編寫一個對文件內容進行簡單加密的程序。
編寫了一個自己的類裝載器,可實現對加密過的類進行裝載和解密。
編寫一個程序調用類加載器加載類,在源程序中不能用該類名定義引用變量,因爲編譯器無法識別這個類。程序中可以除了使用ClassLoader.load方法之外,還可以使用設置
線程的上下文類加載器或者系統類加載器,然後再使用Class.forName。
3)實驗步驟:
對不帶包名的class文件進行加密,加密結果存放到另外一個目錄,例如: java MyClassLoader MyTest.class F:\itcast
運行加載類的程序,結果能夠被正常加載,但打印出來的類裝載器名稱爲AppClassLoader:java MyClassLoader MyTest F:\itcast
用加密後的類文件替換CLASSPATH環境下的類文件,再執行上一步操作就出問題了,錯誤說明是AppClassLoader類裝載器裝載失敗。
刪除CLASSPATH環境下的類文件,再執行上一步操作就沒問題了。
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
public class MyClassLoader extends ClassLoader{
/**
* @param args
*/
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
//源目錄
String srcPath = args[0];
//目標目錄
String destDir = args[1];
//讀取源目錄某個文件
FileInputStream fis = new FileInputStream(srcPath);
//目標的目錄
String destFileName = srcPath.substring(srcPath.lastIndexOf('\\')+1);
//目標的路徑
String destPath = destDir + "\\" + destFileName;
//寫入目標文件。
FileOutputStream fos = new FileOutputStream(destPath);
cypher(fis,fos);
fis.close();
fos.close();
}
//對文件內容加密程序
private static void cypher(InputStream ips ,OutputStream ops) throws Exception{
int b = -1;
while((b=ips.read())!=-1){
//將字節進行異或運算並寫入。(加密)
ops.write(b ^ 0xff);
}
}
private String classDir;
//覆蓋findClass方法。自己寫一個類加載器
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
// TODO Auto-generated method stub
String classFileName = classDir + "\\" + name.substring(name.lastIndexOf('.')+1) + ".class";
try {
FileInputStream fis = new FileInputStream(classFileName);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
cypher(fis,bos);
fis.close();
System.out.println("aaa");
//解密
byte[] bytes = bos.toByteArray();
return defineClass(bytes, 0, bytes.length);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
public MyClassLoader(){
}
public MyClassLoader(String classDir){
this.classDir = classDir;
}
}
[java] view plaincopy
//調用目標類加載器。
Class clazz = new MyClassLoader("itcastlib").loadClass("cn.itcast.day2.ClassLoaderAttachment");
Date d1 = (Date)clazz.newInstance();
System.out.println(d1);
}
四、一個類加載器的高級問題分析
編寫一個能打印出自己的類加載器名稱和當前類加載器的父子結構關係鏈的MyServlet,正常發佈後,看到打印結果爲WebAppClassloader。
把MyServlet.class文件打jar包,放到ext目錄中,重啓tomcat,發現找不到HttpServlet的錯誤。
把servlet.jar也放到ext目錄中,問題解決了,打印的結果是ExtclassLoader 。
父級類加載器加載的類無法引用只能被子級類加載器加載的類,原理如下圖: