1. 程式人生 > >【Java 安全技術探索之路系列:J2SE安全架構】之五:類載入器

【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)方法的實現需要做到以下兩點:

  1. 為來自本地檔案系統或其他來源的類載入其位元組碼。
  2. 呼叫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表示載入的類

  1. 裝載:查詢並載入類的二進位制資料。
  2. 連結
    1. 驗證:確保被載入類的正確性。
    2. 準備:為類的靜態變數分配記憶體,並將其初始化為預設值。
    3. 解析:把類中的符號引用轉換為直接引用。
  3. 初始化:為類的靜態變數賦予正確的初始值。
    1. 建立類的例項。
    2. 訪問某個類或介面的靜態變數,或者對該靜態變數賦值。
    3. 反射:Class.forName(“com.allenwells.MyBlog”);
    4. 初始化父類,初始化父類的子類。
    5. JVM啟動時標明的啟動類(即檔名和類名相同的那個類),此種情況下才會導致類的初始化,
      1. 如果這個類沒有被載入和連結,那就先進行載入和連結。
      2. 如果這個類存在直接父類,並且這個類還沒有初始化(在一個類載入器中,類只能被初始化一次),那就先初始化直接父類(不適用於介面)。
      3. 加入類中存在的初始化語句(如static變數和static塊),那就先執行這些初始化語句。
  4. 載入類:具體流程如下圖所示:

這裡寫圖片描述