【Java 安全技術探索之路系列:J2SE安全架構】之五:類載入器
【Java 安全技術探索之路系列:J2SE安全架構】章節列表
一 類載入器的作用
1.1 名字空間的隔離(Name Space Separation)
把名字空間隔離以防止有意或無意的名字衝突問題。
1.2 包邊界保護(Package Boundary Protection)
類載入器拒絕載入不可靠的類到核心Java包中,這些核心包包含可靠的系統類和其他受限包。
1.3 訪問許可權分配(Access Right Assignment)
類載入器有能力把每個被載入的類和一組授權關聯起來,授權被描述為java.security.Permission型別物件。
1.4 搜尋順序的加強(Search order Enforcement)
類載入機制加強搜尋順序,以防止可靠的類被來自不太可靠的源中的類替換。
二 類載入器的分類
類載入器有一種父子關係,除了引導類載入器,每個類都有一個父類載入器,可以通過getParent()方法獲得,根據規定,類載入器會為它的父類載入器提供一個機會,以便載入任何給定的類,並且只有父類載入失敗時,子類才會去載入給定的類,這種關係也稱為代理模式.
類載入器作為名稱空間
Java程式中包名的存在是為了消除名字的衝突,而在同一個虛擬機器中,可以有兩個類,它們的類名和包名都是相同的,類是由它的全名和類載入器來確定的。這些名字相同的類可以被徹底的區分開而沒有任何衝突,虛擬機器是通過java類的全名和它的類載入器來區分一個java類的
為什麼使用代理模式
所有Java應用都至少引用java.lang.Object類,也就是在執行的時候,java.lang.Object這個類需要被載入到Java虛擬機器中,如果這個載入過程由Java自己的類載入器來完成,則在虛擬機器中會存在多個版本的java.lang.Object類,而且這些類是不相容的,代理模式就是為了保證Java核心庫的型別安全。
類載入器的樹狀圖如下所示:
2.1 系統類載入器
2.1.1 引導類載入器
引導類載入器負責載入系統類,通常從JAR檔案rt.jar中進行載入,它是虛擬機器整體中的一部分,通常是用C語言來實現的,引導類載入器沒有對應的ClassLoader物件。
2.1.2 擴充套件類載入器
擴充套件類載入器用於從jre/lib/ext目錄載入標準的擴充套件,可以將JAR檔案放入該路徑,這樣即使沒有任何類路徑,擴充套件類載入器也可以找到其中的各個類。
注意:如果將JAR檔案放入jre/lib/ext目錄中,並且它的類中有一個類需要呼叫系統類或者擴充套件類,那麼就會遇到麻煩,擴充套件類載入器並不使用類路徑,在使用擴充套件目錄類解決類檔案的衝突之前,要牢記這種情況。
2.1.3 系統類載入器
系統載入器根據Java應用的類路徑(CLASSPATH)來載入Java類,一般來說Java應用的類都是由它來載入的,可以通過ClassLoader.getSystemclassloader()來獲取它。
**上下文類載入 .3
.器**
每一個執行緒都有一個對類載入器的引用,稱為上下文類載入器。主執行緒的上下文類載入器是系統類載入器,當新執行緒建立時,它的上下文載入器就會被設定成為建立執行緒的上下文類載入器。因此,如果你不做任何特殊的操作,所有執行緒的類載入器都會被設定成為系統類載入器。我們也可以通過以下方式為執行緒設定任何型別的類載入器。
設定類載入器
Thread thread = Thread.currentThread();
thread.setContextClassLoader(loader);
獲取類載入器
Thread thread = Thread.currentThread();
ClassLoader loader = thread.getContextClassLoader();
Class class = loader.loadClass(className);
2.2 自定義類載入器
自定義類載入器用來實現某些特殊目的,比如類加密或類檢查等。要編寫自己的類載入器,只需要繼承ClassLoader類,並實現其中的FindClass(String className)方法即可。
FindClass(String className)方法的實現需要做到以下兩點:
- 為來自本地檔案系統或其他來源的類載入其位元組碼。
- 呼叫ClassLoader超類的defineClass()方法,向虛擬機器提供位元組碼。
舉例:下面寫一個檔案系統類載入器,該載入器可以載入儲存在檔案系統上的Java位元組碼。
public class FileSystemClassLoader extends ClassLoader {
private String rootDir;
public FileSystemClassLoader(String rootDir) {
this.rootDir = rootDir;
}
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] classData = getClassData(name);
if (classData == null) {
throw new ClassNotFoundException();
}
else {
return defineClass(name, classData, 0, classData.length);
}
}
private byte[] getClassData(String className) {
String path = classNameToPath(className);
try {
InputStream ins = new FileInputStream(path);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int bufferSize = 4096;
byte[] buffer = new byte[bufferSize];
int bytesNumRead = 0;
while ((bytesNumRead = ins.read(buffer)) != -1) {
baos.write(buffer, 0, bytesNumRead);
}
return baos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
private String classNameToPath(String className) {
return rootDir + File.separatorChar
+ className.replace('.', File.separatorChar) + ".class";
}
}
三 類的載入流程
Java類載入器負責載入Java類位元組碼到Java虛擬機器中。一般說來,類的編譯和載入會經過以下幾個流程。
注:MyClass表示載入的類
- 裝載:查詢並載入類的二進位制資料。
- 連結
- 驗證:確保被載入類的正確性。
- 準備:為類的靜態變數分配記憶體,並將其初始化為預設值。
- 解析:把類中的符號引用轉換為直接引用。
- 初始化:為類的靜態變數賦予正確的初始值。
- 建立類的例項。
- 訪問某個類或介面的靜態變數,或者對該靜態變數賦值。
- 反射:Class.forName(“com.allenwells.MyBlog”);
- 初始化父類,初始化父類的子類。
- JVM啟動時標明的啟動類(即檔名和類名相同的那個類),此種情況下才會導致類的初始化,
- 如果這個類沒有被載入和連結,那就先進行載入和連結。
- 如果這個類存在直接父類,並且這個類還沒有初始化(在一個類載入器中,類只能被初始化一次),那就先初始化直接父類(不適用於介面)。
- 加入類中存在的初始化語句(如static變數和static塊),那就先執行這些初始化語句。
- 載入類:具體流程如下圖所示: