了解Java ClassLoader
1.介紹
2.ClassLoader的結構
3.Compiling ClassLoader
4.java2 中ClassLoader的變動
5.源代碼
---------------------------------------------------------------------------
第一章 介紹
什么是 ClassLoader
在流行的商業化編程語言中,Java 語言由于在 Java 虛擬機 (JVM) 上運行而顯得與眾不同。這意味著已編譯的程序是一種特殊的、獨立于平臺的格式,并非依賴于它們所運行的機器。在很大程度上,這種格式不同于傳統的可執行程序格式。
與 C 或 C++ 編寫的程序不同,Java 程序并不是一個可執行文件,而是由許多獨立的類文件組成,每一個文件對應于一個 Java 類。
此外,這些類文件并非立即全部都裝入內存,而是根據程序需要裝入內存。ClassLoader 是 JVM 中將類裝入內存的那部分。
而且,Java ClassLoader 就是用 Java 語言編寫的。這意味著創建您自己的 ClassLoader 非常容易,不必了解 JVM 的微小細節。
如果 JVM 已經有一個 ClassLoader,那么為什么還要編寫另一個呢?問得好。缺省的 ClassLoader 只知道如何從本地文件系統裝入類文件。不過這只適合于常規情況,即已全部編譯完 Java 程序,并且計算機處于等待狀態。
但 Java 語言最具新意的事就是 JVM 可以非常容易地從那些非本地硬盤或從網絡上獲取類。例如,瀏覽者可以使用定制的 ClassLoader 從 Web 站點裝入可執行內容。
有許多其它方式可以獲取類文件。除了簡單地從本地或網絡裝入文件以外,可以使用定制的 ClassLoader 完成以下任務:
- 在執行非置信代碼之前,自動驗證數字簽名
- 使用用戶提供的密碼透明地解密代碼
- 動態地創建符合用戶特定需要的定制化構建類
定制 ClassLoader 示例
如果使用過 JDK 或任何基于 Java 瀏覽器中的 Applet 查看器,那么您差不多肯定使用過定制的 ClassLoader。 Sun 最初發布 Java 語言時,其中最令人興奮的一件事是觀看這項新技術是如何執行在運行時從遠程的 Web 服務器裝入的代碼。(此外,還有更令人興奮的事 -- Java 技術提供了一種便于編寫代碼的強大語言。)更一些令人激動的是它可以執行從遠程 Web 服務器通過 HTTP 連接發送過來的字節碼。 此項功能歸功于 Java 語言可以安裝定制 ClassLoader。Applet 查看器包含一個 ClassLoader,它不在本地文件系統中尋找類,而是訪問遠程服務器上的 Web 站點,經過 HTTP 裝入原始的字節碼文件,并把它們轉換成 JVM 內的類。 瀏覽器和 Applet 查看器中的 ClassLoaders 還可以做其它事情:它們支持安全性以及使不同的 Applet 在不同的頁面上運行而互不干擾。 Luke Gorrie 編寫的 Echidna 是一個開放源碼包,它可以使您在單個虛擬機上運行多個 Java 應用程序。(請參閱進一步了解和參考資料。)它使用定制的 ClassLoader,通過向每個應用程序提供該類文件的自身副本,以防止應用程序互相干擾。 我們的 ClassLoader 示例
了解了 ClassLoader 如何工作以及如何編寫 ClassLoader 之后,我們將創建稱作 CompilingClassLoader (CCL) 的 Classloader。CCL 為我們編譯 Java 代碼,而無需要我們干涉這個過程。它基本上就類似于直接構建到運行時系統中的 "make" 程序。 注:進一步了解之前,應注意在 JDK 版本 1.2 中已改進了 ClassLoader 系統的某些方面(即 Java 2 平臺)。本教程是按 JDK 版本 1.0 和 1.1 寫的,但也可以在以后的版本中運行。 Java 2 中 ClassLoader 的變動描述了 Java 版本 1.2 中的變動,并提供了一些詳細信息,以便修改 ClassLoader 來利用這些變動。
------------------------------------------------------------------------------------------------------
ClassLoader 的基本目標是對類的請求提供服務。當 JVM 需要使用類時,它根據名稱向 ClassLoader 請求這個類,然后 ClassLoader 試圖返回一個表示這個類的 Class
對象。
通過覆蓋對應于這個過程不同階段的方法,可以創建定制的 ClassLoader。
在本章的其余部分,您會學習 Java ClassLoader 的關鍵方法。您將了解每一個方法的作用以及它是如何適合裝入類文件這個過程的。您也會知道,創建自己的 ClassLoader 時,需要編寫什么代碼。
在下一章中,您將會利用這些知識來使用我們的 ClassLoader 示例 -- CompilingClassLoader。
方法 loadClass
在 Java 版本 1.1 和以前的版本中, 方法
方法
對于定制的 ClassLoader,只有在嘗試其它方法裝入類之后,再使用 其工作流程如下: 在大多數定制 ClassLoaders 中,首先調用 方法 正如前面所提到的,可以不完全地(不帶解析)裝入類,也可以完全地(帶解析)裝入類。當編寫我們自己的 方法 組裝 ClassLoader.loadClass()
是 ClassLoader 的入口點。其特征如下: Class loadClass( String name, boolean resolve );
name
參數指定了 JVM 需要的類的名稱,該名稱以包表示法表示,如 Foo
或 java.lang.Object
。 resolve
參數告訴方法是否需要解析類。在準備執行類之前,應考慮類解析。并不總是需要解析。如果 JVM 只需要知道該類是否存在或找出該類的超類,那么就不需要解析。 loadClass
方法是創建定制的 ClassLoader 時唯一需要覆蓋的方法。(Java 2 中 ClassLoader 的變動提供了關于 Java 1.2 中 findClass()
方法的信息。)
defineClass
defineClass
方法是 ClassLoader 的主要訣竅。該方法接受由原始字節組成的數組并把它轉換成 Class
對象。原始數組包含如從文件系統或網絡裝入的數據。 defineClass
管理 JVM 的許多復雜、神秘和倚賴于實現的方面 -- 它把字節碼分析成運行時數據結構、校驗有效性等等。不必擔心,您無需親自編寫它。事實上,即使您想要這么做也不能覆蓋它,因為它已被標記成最終的。
findSystemClass
findSystemClass
方法從本地文件系統裝入文件。它在本地文件系統中尋找類文件,如果存在,就使用 defineClass
將原始字節轉換成 Class
對象,以將該文件轉換成類。當運行 Java 應用程序時,這是 JVM 正常裝入類的缺省機制。(Java 2 中 ClassLoader 的變動提供了關于 Java 版本 1.2 這個過程變動的詳細信息。) findSystemClass
。原因很簡單:ClassLoader 是負責執行裝入類的特殊步驟,不是負責所有類。例如,即使 ClassLoader 從遠程的 Web 站點裝入了某些類,仍然需要在本地機器上裝入大量的基本 Java 庫。而這些類不是我們所關心的,所以要 JVM 以缺省方式裝入它們:從本地文件系統。這就是 findSystemClass
的用途。
findSystemClass
,使它從文件系統裝入該類。 findSystemClass
以節省在本地就可以裝入的許多 Java 庫類而要在遠程 Web 站點上查找所花的時間。然而,正如,在下一章節所看到的,直到確信能自動編譯我們的應用程序代碼時,才讓 JVM 從本地文件系統裝入類。
resolveClass
loadClass
時,可以調用 resolveClass
,這取決于 loadClass
的 resolve
參數的值。findLoadedClass
findLoadedClass
充當一個緩存:當請求 loadClass
裝入類時,它調用該方法來查看 ClassLoader 是否已裝入這個類,這樣可以避免重新裝入已存在類所造成的麻煩。應首先調用該方法。
讓我們看一下如何組裝所有方法。
我們的 loadClass
實現示例執行以下步驟。(這里,我們沒有指定生成類文件是采用了哪種技術 -- 它可以是從 Net 上裝入、或者從歸檔文件中提取、或者實時編譯。無論是哪一種,那是種特殊的神奇方式,使我們獲得了原始類文件字節。)
- 調用
findLoadedClass
來查看是否存在已裝入的類。
- 如果沒有,那么采用那種特殊的神奇方式來獲取原始字節。
- 如果已有原始字節,調用
defineClass
將它們轉換成Class
對象。
- 如果沒有原始字節,然后調用
findSystemClass
查看是否從本地文件系統獲取類。
- 如果
resolve
參數是true
,那么調用resolveClass
解析Class
對象。
- 如果還沒有類,返回
ClassNotFoundException
。
- 否則,將類返回給調用程序。
現在您已經了解了 ClassLoader 的工作原理,現在該構建一個了。在下一章中,我們將討論 CCL。
---------------------------------------------------------------------------------------------
第三章:Compiling ClassLoader CCL 揭密
我們的 ClassLoader (CCL) 的任務是確保代碼被編譯和更新。 下面描述了它的工作方式:
在深入討論之前,應該先退一步,討論 Java 編譯。通常,Java 編譯器不只是編譯您要求它編譯的類。它還會編譯其它類,如果這些類是您要求編譯的類所需要的類。 CCL 逐個編譯應用程序中的需要編譯的每一個類。但一般來說,在編譯器編譯完第一個類后,CCL 會查找所有需要編譯的類,然后編譯它。為什么?Java 編譯器類似于我們正在使用的規則:如果類不存在,或者與它的源碼相比,它比較舊,那么它需要編譯。其實,Java 編譯器在 CCL 之前的一個步驟,它會做大部分的工作。 當 CCL 編譯它們時,會報告它正在編譯哪個應用程序上的類。在大多數的情況下,CCL 會在程序中的主類上調用編譯器,它會做完所有要做的 -- 編譯器的單一調用已足夠了。 然而,有一種情形,在第一步時不會編譯某些類。如果使用 使用 CompilationClassLoader
要使用 CCL,必須以特殊方式調用程序。不能直接運行該程序,如: 應以下列方式運行它: CCLRun 是一個特殊的存根程序,它創建 CompilingClassLoader 并用它來裝入程序的主類,以確保通過 CompilingClassLoader 來裝入整個程序。CCLRun 使用 Java Reflection API 來調用特定類的主方法并把參數傳遞給它。有關詳細信息,請參閱源代碼。 運行示例
源碼包括了一組小類,它們演示了工作方式。主程序是 每個類都聲明已被裝入并運行。現在用源代碼來試一下。編譯 CCLRun 和 CompilingClassLoader。確保不要編譯其它類( 請注意,首先調用編譯器, -------------------------------------------------------------------------------------- 第四章:java2 中ClassLoader的變動 概述
在 Java 版本 1.2 和以后的版本中,對 ClassLoader 做了一些改進。任何為老系統編寫的代碼可以在新版本中運行,但新系統為您提供了一些便利。 新模型是委托模型,這意味著如果 ClassLoader 不能找到類,它會請求父代 ClassLoader 來執行此項任務。所有 ClassLoaders 的根是系統 ClassLoader,它會以缺省方式裝入類 -- 即,從本地文件系統。
定制編寫的 在 Java 1.2 中 這種方式的好處是您可能不一定要覆蓋 新方法: 新方法: 如果覆蓋 新方法:
為了將類請求委托給父代 ClassLoader,這個新方法允許 ClassLoader 獲取它的父代 ClassLoader。當使用特殊方法,定制的 ClassLoader 不能找到類時,可以使用這種方法。 父代 ClassLoader 被定義成創建該 ClassLoader 所包含代碼的對象的 ClassLoader。 ---------------------------------------------------------------------------------- 第五章.源代碼
Java 編譯的工作方式
ClassNotFoundException
。
findSystemClass
來尋找該類。
ClassNotFoundException
。
Class.forName
方法,通過名稱來裝入類,Java 編譯器會不知道這個類時所需要的。在這種情況下,您會看到 CCL 再次運行 Java 編譯器來編譯這個類。在源代碼中演示了這個過程。
% java Foo arg1 arg2
% java CCLRun Foo arg1 arg2
Foo
類,它創建類 Bar
的實例。類 Bar
創建另一個類 Baz
的實例,它在 baz
包內,這是為了展示 CCL 是如何處理子包里的代碼。Bar
也是通過名稱裝入的,其名稱為 Boo
,這用來展示它也能與 CCL 工作。 Foo
、Bar
、Baz
和 Boo
),否則將不會使用 CCL,因為這些類已經編譯過了。
% java CCLRun Foo arg1 arg2
CCL: Compiling Foo.java...
foo! arg1 arg2
bar! arg1 arg2
baz! arg1 arg2
CCL: Compiling Boo.java...
Boo!
Foo.java
管理 Bar
和 baz.Baz
。直到 Bar
通過名稱來裝入 Boo
時,被調用它,這時 CCL 會再次調用編譯器來編譯它。
loadClass
的缺省實現loadClass
方法一般嘗試幾種方式來裝入所請求的類,如果您編寫許多類,會發現一次次地在相同的、很復雜的方法上編寫變量。 loadClass
的實現嵌入了大多數查找類的一般方法,并使您通過覆蓋 findClass
方法來定制它,在適當的時候 findClass
會調用 loadClass
。 loadClass
;只要覆蓋 findClass
就行了,這減少了工作量。
findClass
loadClass
的缺省實現調用這個新方法。findClass
的用途包含您的 ClassLoader 的所有特殊代碼,而無需要復制其它代碼(例如,當專門的方法失敗時,調用系統 ClassLoader)。getSystemClassLoader
findClass
或 loadClass
,getSystemClassLoader
使您能以實際 ClassLoader
對象來訪問系統 ClassLoader(而不是固定的從 findSystemClass
調用它)。getParent
CompilingClassLoader.java
以下是 CompilingClassLoader.java 的源代碼
// $Id$
import java.io.*;
/*
A CompilingClassLoader compiles your Java source on-the-fly. It checks
for nonexistent .class files, or .class files that are older than their
corresponding source code.*/
public class CompilingClassLoader extends ClassLoader
{
// Given a filename, read the entirety of that file from disk
// and return it as a byte array.
private byte[] getBytes( String filename ) throws IOException {
// Find out the length of the file
File file = new File( filename );
long len = file.length();
// Create an array that's just the right size for the file's
// contents
byte raw[] = new byte[(int)len];
// Open the file
FileInputStream fin = new FileInputStream( file );
// Read all of it into the array; if we don't get all,
// then it's an error.
int r = fin.read( raw );
if (r != len)
throw new IOException( "Can't read all, "+r+" != "+len );
// Don't forget to close the file!
fin.close();
// And finally return the file contents as an array
return raw;
}
// Spawn a process to compile the java source code file
// specified in the 'javaFile' parameter. Return a true if
// the compilation worked, false otherwise.
private boolean compile( String javaFile ) throws IOException {
// Let the user know what's going on
System.out.println( "CCL: Compiling "+javaFile+"..." );
// Start up the compiler
Process p = Runtime.getRuntime().exec( "javac "+javaFile );
// Wait for it to finish running
try {
p.waitFor();
} catch( InterruptedException ie ) { System.out.println( ie ); }
// Check the return code, in case of a compilation error
int ret = p.exitValue();
// Tell whether the compilation worked
return ret==0;
}
// The heart of the ClassLoader -- automatically compile
// source as necessary when looking for class files
public Class loadClass( String name, boolean resolve )
throws ClassNotFoundException {
// Our goal is to get a Class object
Class clas = null;
// First, see if we've already dealt with this one
clas = findLoadedClass( name );
//System.out.println( "findLoadedClass: "+clas );
// Create a pathname from the class name
// E.g. java.lang.Object => java/lang/Object
String fileStub = name.replace( '.', '/' );
// Build objects pointing to the source code (.java) and object
// code (.class)
String javaFilename = fileStub+".java";
String classFilename = fileStub+".class";
File javaFile = new File( javaFilename );
File classFile = new File( classFilename );
//System.out.println( "j "+javaFile.lastModified()+" c "+
// classFile.lastModified() );
// First, see if we want to try compiling. We do if (a) there
// is source code, and either (b0) there is no object code,
// or (b1) there is object code, but it's older than the source
if (javaFile.exists() &&
(!classFile.exists() ||
javaFile.lastModified() > classFile.lastModified())) {
try {
// Try to compile it. If this doesn't work, then
// we must declare failure. (It's not good enough to use
// and already-existing, but out-of-date, classfile)
if (!compile( javaFilename ) || !classFile.exists()) {
throw new ClassNotFoundException( "Compile failed: "+javaFilename );
}
} catch( IOException ie ) {
// Another place where we might come to if we fail
// to compile
throw new ClassNotFoundException( ie.toString() );
}
}
// Let's try to load up the raw bytes, assuming they were
// properly compiled, or didn't need to be compiled
try {
// read the bytes
byte raw[] = getBytes( classFilename );
// try to turn them into a class
clas = defineClass( name, raw, 0, raw.length );
} catch( IOException ie ) {
// This is not a failure! If we reach here, it might
// mean that we are dealing with a class in a library,
// such as java.lang.Object
}
//System.out.println( "defineClass: "+clas );
// Maybe the class is in a library -- try loading
// the normal way
if (clas==null) {
clas = findSystemClass( name );
}
//System.out.println( "findSystemClass: "+clas );
// Resolve the class, if any, but only if the "resolve"
// flag is set to true
if (resolve && clas != null)
resolveClass( clas );
// If we still don't have a class, it's an error
if (clas == null)
throw new ClassNotFoundException( name );
// Otherwise, return the class
return clas;
}
}
CCRun.java
以下是 CCRun.java 的源代碼
Foo.java
// $Id$
import java.lang.reflect.*;
/*
CCLRun executes a Java program by loading it through a
CompilingClassLoader.
*/
public class CCLRun
{
static public void main( String args[] ) throws Exception {
// The first argument is the Java program (class) the user
// wants to run
String progClass = args[0];
// And the arguments to that program are just
// arguments 1..n, so separate those out into
// their own array
String progArgs[] = new String[args.length-1];
System.arraycopy( args, 1, progArgs, 0, progArgs.length );
// Create a CompilingClassLoader
CompilingClassLoader ccl = new CompilingClassLoader();
// Load the main class through our CCL
Class clas = ccl.loadClass( progClass );
// Use reflection to call its main() method, and to
// pass the arguments in.
// Get a class representing the type of the main method's argument
Class mainArgType[] = { (new String[0]).getClass() };
// Find the standard main method in the class
Method main = clas.getMethod( "main", mainArgType );
// Create a list containing the arguments -- in this case,
// an array of strings
Object argsArray[] = { progArgs };
// Call the method
main.invoke( null, argsArray );
}
}
以下是 Foo.java 的源代碼
Bar.java
// $Id$
public class Foo
{
static public void main( String args[] ) throws Exception {
System.out.println( "foo! "+args[0]+" "+args[1] );
new Bar( args[0], args[1] );
}
}
以下是 Bar.java 的源代碼baz/Baz.java
// $Id$
import baz.*;
public class Bar
{
public Bar( String a, String b ) {
System.out.println( "bar! "+a+" "+b );
new Baz( a, b );
try {
Class booClass = Class.forName( "Boo" );
Object boo = booClass.newInstance();
} catch( Exception e ) {
e.printStackTrace();
}
}
}
以下是 baz/Baz.java 的源代碼
Boo.java
// $Id$
package baz;
public class Baz
{
public Baz( String a, String b ) {
System.out.println( "baz! "+a+" "+b );
}
}
以下是 Boo.java 的源代碼
// $Id$
public class Boo
{
public Boo() {
System.out.println( "Boo!" );
}
}
posted on 2008-01-25 13:52 菠蘿 閱讀(330) 評論(0) 編輯 收藏 所屬分類: java