ClassLoader原理

http://jamesdu.blogchina.com/349567.html                                      

JVM規範定義了兩種型別的類裝載器:啟動內裝載器(bootstrap)和使用者自定義裝載器(user-defined class loader)




一.    ClassLoader基本概念
1.ClassLoader分類
類裝載器是用來把類(class)裝載進JVM的。
JVM規範定義了兩種型別的類裝載器:啟動內裝載器(bootstrap)和使用者自定義裝載器(user-defined class loader)


JVM在執行時會產生三個ClassLoader:Bootstrap ClassLoader、Extension ClassLoader和AppClassLoader.
Bootstrap是用C++編寫的,我們在Java中看不到它,是null,是JVM自帶的類裝載器,用來裝載核心類庫,如java.lang.*等。
AppClassLoader的Parent是ExtClassLoader,而ExtClassLoader的Parent為Bootstrap ClassLoader。

Java提供了抽象類ClassLoader,所有使用者自定義類裝載器都例項化自ClassLoader的子類。 System Class Loader是一個特殊的使用者自定義類裝載器,由JVM的實現者提供,在程式設計者不特別指定裝載器的情況下預設裝載使用者類
。系統類裝載器可以通過ClassLoader.getSystemClassLoader() 方法得到。

例1,測試你所使用的JVM的ClassLoader
/*LoaderSample1.java*/

public classLoaderSample1 {
   
public staticvoidmain(String[] args) {
        Class c;
        ClassLoader cl;
        cl
= ClassLoader.getSystemClassLoader();
        System.out.println(cl);
       
while (cl != null) {
            cl
= cl.getParent();
            System.out.println(cl);
        }
       
try {
            c
= Class.forName( " java.lang.Object " );
            cl
= c.getClassLoader();
            System.out.println(
" java.lang.Object's loader is " + cl);
            c
= Class.forName( " LoaderSample1 " );
            cl
= c.getClassLoader();
            System.out.println(
" LoaderSample1's loader is " + cl);
        }
catch (Exception e) {
            e.printStackTrace();
        }
    }
}


在我的機器上(Sun Java 1.4.2)的執行結果
sun.misc.Launcher$AppClassLoader@1a0c10f
sun.misc.Launcher$ExtClassLoader@e2eec8
null
java.lang.Object's loader is null
LoaderSample1's loader is sun.misc.Launcher$AppClassLoader@1a0c10f

第一行表示,系統類裝載器例項化自類sun.misc.Launcher$AppClassLoader
第二行表示,系統類裝載器的parent例項化自類sun.misc.Launcher$ExtClassLoader
第三行表示,系統類裝載器parent的parent為bootstrap
第四行表示,核心類java.lang.Object是由bootstrap裝載的
第五行表示,使用者類LoaderSample1是由系統類裝載器裝載的


二.parent delegation模型
從1.2版本開始,Java引入了雙親委託模型,從而更好的保證Java平臺的安全。在此模型下,當一個裝載器被請求裝載某個類時,它首先委託自己的parent去裝載,若parent能裝載,則返回這個類所對應的Class物件,若parent不能裝載,則由parent的請求者去裝載

圖 1 parent delegation模型
如圖1所示,loader2的parent為loader1,loader1的parent為system class loader。假設loader2被要求裝載類MyClass,在parent delegation模型下,loader2首先請求loader1代為裝載,loader1再請求系統類裝載器去裝載MyClass。若系統裝載器能成功裝載,則將MyClass所對應的Class物件的reference返回給loader1,loader1再將reference返回給loader2,從而成功將類MyClass裝載進虛擬機器。若系統類裝載器不能裝載MyClass,loader1會嘗試裝載MyClass,若loader1也不能成功裝載,loader2會嘗試裝載。若所有的parent及loader2本身都不能裝載,則裝載失敗。

若有一個能成功裝載,實際裝載的類裝載器被稱為定義類裝載器,所有能成功返回Class物件的裝載器(包括定義類裝載器)被稱為初始類裝載器。如圖1所示,假設loader1實際裝載了MyClass,則loader1為MyClass的定義類裝載器,loader2和loader1為MyClass的初始類裝載器。

需要指出的是,Class Loader是物件,它的父子關係和類的父子關係沒有任何關係。

那麼parent delegation模型為什麼更安全了?因為在此模型下使用者自定義的類裝載器不可能裝載應該由父親裝載器裝載的可靠類,從而防止不可靠甚至惡意的程式碼代替由父親裝載器裝載的可靠程式碼。實際上,類裝載器的編寫者可以自由選擇不用把請求委託給parent,但正如上所說,會帶來安全的問題。


三.名稱空間及其作用
每個類裝載器有自己的名稱空間,名稱空間由所有以此裝載器為創始類裝載器的類組成。不同名稱空間的兩個類是不可見的,但只要得到類所對應的Class物件的reference,還是可以訪問另一名稱空間的類。

例2演示了一個名稱空間的類如何使用另一名稱空間的類。在例子中,LoaderSample2由系統類裝載器裝載,LoaderSample3由自定義的裝載器loader負責裝載,兩個類不在同一名稱空間,但LoaderSample2得到了LoaderSample3所對應的Class物件的reference,所以它可以訪問LoaderSampl3中公共的成員(如age)。
例2不同名稱空間的類的訪問
/*LoaderSample2.java*/

import java.net.*;
import java.lang.reflect.*;
public classLoaderSample2 {
   
public staticvoidmain(String[] args) {
       
try {
            String path
= System.getProperty( " user.dir " );
            URL[] us
= { new URL("file:// " + path + "/sub/" )};
            ClassLoader loader
= new URLClassLoader(us);
            Class c
= loader.loadClass( " LoaderSample3 " );
            Object o
= c.newInstance();
            Field f
= c.getField( " age " );
           
int age = f.getInt(o);
            System.out.println(
" age is " +age);
        }
catch (Exception e) {
            e.printStackTrace();
        }
    }
}


/*sub/Loadersample3.java*/

public classLoaderSample3 {
   
static {
        System.out.println(
" LoaderSample3 loaded " );
    }
   
public intage= 30;
}

編譯:javac LoaderSample2.java; javac sub/LoaderSample3.java
執行:java LoaderSample2
LoaderSample3 loaded
age is 30
從執行結果中可以看出,在類LoaderSample2中可以建立處於另一名稱空間的類LoaderSample3中的物件並可以訪問其公共成員age。
執行時包(runtime package)
由同一類裝載器定義裝載的屬於相同包的類組成了執行時包,決定兩個類是不是屬於同一個執行時包,不僅要看它們的包名是否相同,還要看的定義類裝載器是否相同。只有屬於同一執行時包的類才能互相訪問包可見的類和成員。這樣的限制避免了使用者自己的程式碼冒充核心類庫的類訪問核心類庫包可見成員的情況。假設使用者自己定義了一個類java.lang.Yes,並用使用者自定義的類裝載器裝載,由於java.lang.Yes和核心類庫java.lang.*由不同的裝載器裝載,它們屬於不同的執行時包,所以java.lang.Yes不能訪問核心類庫java.lang中類的包可見的成員。

總結
名稱空間並沒有完全禁止屬於不同空間的類的互相訪問,雙親委託模型加強了Java的安全,執行時包增加了對包可見成員的保護。

二.    擴充套件ClassLoader方法
我們目的是從本地檔案系統使用我們實現的類裝載器裝載一個類。為了建立自己的類裝載器我們應該擴充套件ClassLoader類,這是一個抽象類。我們建立一個FileClassLoader extends ClassLoader。我們需要覆蓋ClassLoader中的findClass(String name)方法,這個方法通過類的名字而得到一個Class物件。

    public Class findClass(String name)
    {
       
byte [] data = loadClassData(name);
       
return defineClass(name, data, 0 , data.length);
    }


   我們還應該提供一個方法loadClassData(String name),通過類的名稱返回class檔案的字
節陣列。然後使用ClassLoader提供的defineClass()方法我們就可以返回Class物件了。

    public byte [] loadClassData(String name)
    {
        FileInputStream fis
= null ;
       
byte [] data = null;
       
try
        {
            fis
= new FileInputStream(newFile(drive +name +fileType));
            ByteArrayOutputStream baos
=newByteArrayOutputStream();
           
int ch = 0;
           
while ((ch = fis.read())!=-1)
            {
                baos.write(ch);
              
            }
            data
= baos.toByteArray();
        }
catch (IOException e)
        {
            e.printStackTrace();
        }
       
       
return data;
    }