在流行的商業(yè)化編程語(yǔ)言中,Java 語(yǔ)言由于在 Java 虛擬機 (JVM) 上運行而顯得與眾不同。這意味著(zhù)已編譯的程序是一種特殊的、獨立于平臺的格式,并非依賴(lài)于它們所運行的機器。在很大程度上,這種格式不同于傳統的可執行程序格式。
與 C 或 C++ 編寫(xiě)的程序不同,Java 程序并不是一個(gè)可執行文件,而是由許多獨立的類(lèi)文件組成,每一個(gè)文件對應于一個(gè) Java 類(lèi)。
此外,這些類(lèi)文件并非立即全部都裝入內存,而是根據程序需要裝入內存。ClassLoader 是 JVM 中將類(lèi)裝入內存的那部分。
而且,Java ClassLoader 就是用 Java 語(yǔ)言編寫(xiě)的。這意味著(zhù)創(chuàng )建您自己的 ClassLoader 非常容易,不必了解 JVM 的微小細節。
如果 JVM 已經(jīng)有一個(gè) ClassLoader,那么為什么還要編寫(xiě)另一個(gè)呢?問(wèn)得好。缺省的 ClassLoader 只知道如何從本地文件系統裝入類(lèi)文件。不過(guò)這只適合于常規情況,即已全部編譯完 Java 程序,并且計算機處于等待狀態(tài)。
但 Java 語(yǔ)言最具新意的事就是 JVM 可以非常容易地從那些非本地硬盤(pán)或從網(wǎng)絡(luò )上獲取類(lèi)。例如,瀏覽者可以使用定制的 ClassLoader 從 Web 站點(diǎn)裝入可執行內容。
有許多其它方式可以獲取類(lèi)文件。除了簡(jiǎn)單地從本地或網(wǎng)絡(luò )裝入文件以外,可以使用定制的 ClassLoader 完成以下任務(wù):
定制 ClassLoader 示例
如果使用過(guò) JDK 或任何基于 Java 瀏覽器中的 Applet 查看器,那么您差不多肯定使用過(guò)定制的 ClassLoader。
Sun 最初發(fā)布 Java 語(yǔ)言時(shí),其中最令人興奮的一件事是觀(guān)看這項新技術(shù)是如何執行在運行時(shí)從遠程的 Web 服務(wù)器裝入的代碼。(此外,還有更令人興奮的事 -- Java 技術(shù)提供了一種便于編寫(xiě)代碼的強大語(yǔ)言。)更一些令人激動(dòng)的是它可以執行從遠程 Web 服務(wù)器通過(guò) HTTP 連接發(fā)送過(guò)來(lái)的字節碼。
此 項功能歸功于 Java 語(yǔ)言可以安裝定制 ClassLoader。Applet 查看器包含一個(gè) ClassLoader,它不在本地文件系統中尋找類(lèi),而是訪(fǎng)問(wèn)遠程服務(wù)器上的 Web 站點(diǎn),經(jīng)過(guò) HTTP 裝入原始的字節碼文件,并把它們轉換成 JVM 內的類(lèi)。
瀏覽器和 Applet 查看器中的 ClassLoaders 還可以做其它事情:它們支持安全性以及使不同的 Applet 在不同的頁(yè)面上運行而互不干擾。
Luke Gorrie 編寫(xiě)的 Echidna 是一個(gè)開(kāi)放源碼包,它可以使您在單個(gè)虛擬機上運行多個(gè) Java 應用程序。(請參閱進(jìn)一步了解和參考資料。)它使用定制的 ClassLoader,通過(guò)向每個(gè)應用程序提供該類(lèi)文件的自身副本,以防止應用程序互相干擾。
我們的 ClassLoader 示例
了解了 ClassLoader 如何工作以及如何編寫(xiě) ClassLoader 之后,我們將創(chuàng )建稱(chēng)作 CompilingClassLoader (CCL) 的 Classloader。CCL 為我們編譯 Java 代碼,而無(wú)需要我們干涉這個(gè)過(guò)程。它基本上就類(lèi)似于直接構建到運行時(shí)系統中的 "make" 程序。 注:進(jìn)一步了解之前,應注意在 JDK 版本 1.2 中已改進(jìn)了 ClassLoader 系統的某些方面(即 Java 2 平臺)。本教程是按 JDK 版本 1.0 和 1.1 寫(xiě)的,但也可以在以后的版本中運行。 Java 2 中 ClassLoader 的變動(dòng)描述了 Java 版本 1.2 中的變動(dòng),并提供了一些詳細信息,以便修改 ClassLoader 來(lái)利用這些變動(dòng)。
------------------------------------------------------------------------------------------------------
ClassLoader 的基本目標是對類(lèi)的請求提供服務(wù)。當 JVM 需要使用類(lèi)時(shí),它根據名稱(chēng)向 ClassLoader 請求這個(gè)類(lèi),然后 ClassLoader 試圖返回一個(gè)表示這個(gè)類(lèi)的 Class 對象。
通過(guò)覆蓋對應于這個(gè)過(guò)程不同階段的方法,可以創(chuàng )建定制的 ClassLoader。
在本章的其余部分,您會(huì )學(xué)習 Java ClassLoader 的關(guān)鍵方法。您將了解每一個(gè)方法的作用以及它是如何適合裝入類(lèi)文件這個(gè)過(guò)程的。您也會(huì )知道,創(chuàng )建自己的 ClassLoader 時(shí),需要編寫(xiě)什么代碼。
在下一章中,您將會(huì )利用這些知識來(lái)使用我們的 ClassLoader 示例 -- CompilingClassLoader。
方法 loadClass
ClassLoader.loadClass() 是 ClassLoader 的入口點(diǎn)。其特征如下:
Class loadClass( String name, boolean resolve );
name 參數指定了 JVM 需要的類(lèi)的名稱(chēng),該名稱(chēng)以包表示法表示,如 Foo 或 java.lang.Object。
resolve 參數告訴方法是否需要解析類(lèi)。在準備執行類(lèi)之前,應考慮類(lèi)解析。并不總是需要解析。如果 JVM 只需要知道該類(lèi)是否存在或找出該類(lèi)的超類(lèi),那么就不需要解析。
在 Java 版本 1.1 和以前的版本中,loadClass 方法是創(chuàng )建定制的 ClassLoader 時(shí)唯一需要覆蓋的方法。(Java 2 中 ClassLoader 的變動(dòng)提供了關(guān)于 Java 1.2 中 findClass() 方法的信息。)
方法 defineClass
defineClass 方法是 ClassLoader 的主要訣竅。該方法接受由原始字節組成的數組并把它轉換成 Class 對象。原始數組包含如從文件系統或網(wǎng)絡(luò )裝入的數據。
defineClass 管理 JVM 的許多復雜、神秘和倚賴(lài)于實(shí)現的方面 -- 它把字節碼分析成運行時(shí)數據結構、校驗有效性等等。不必擔心,您無(wú)需親自編寫(xiě)它。事實(shí)上,即使您想要這么做也不能覆蓋它,因為它已被標記成最終的。
方法 findSystemClass
findSystemClass 方法從本地文件系統裝入文件。它在本地文件系統中尋找類(lèi)文件,如果存在,就使用 defineClass 將原始字節轉換成 Class 對象,以將該文件轉換成類(lèi)。當運行 Java 應用程序時(shí),這是 JVM 正常裝入類(lèi)的缺省機制。(Java 2 中 ClassLoader 的變動(dòng)提供了關(guān)于 Java 版本 1.2 這個(gè)過(guò)程變動(dòng)的詳細信息。)
對于定制的 ClassLoader,只有在嘗試其它方法裝入類(lèi)之后,再使用 findSystemClass。原因很簡(jiǎn)單:ClassLoader 是負責執行裝入類(lèi)的特殊步驟,不是負責所有類(lèi)。例如,即使 ClassLoader 從遠程的 Web 站點(diǎn)裝入了某些類(lèi),仍然需要在本地機器上裝入大量的基本 Java 庫。而這些類(lèi)不是我們所關(guān)心的,所以要 JVM 以缺省方式裝入它們:從本地文件系統。這就是 findSystemClass 的用途。
其工作流程如下:
findSystemClass,使它從文件系統裝入該類(lèi)。 在大多數定制 ClassLoaders 中,首先調用 findSystemClass 以節省在本地就可以裝入的許多 Java 庫類(lèi)而要在遠程 Web 站點(diǎn)上查找所花的時(shí)間。然而,正如,在下一章節所看到的,直到確信能自動(dòng)編譯我們的應用程序代碼時(shí),才讓 JVM 從本地文件系統裝入類(lèi)。
方法 resolveClass
正如前面所提到的,可以不完全地(不帶解析)裝入類(lèi),也可以完全地(帶解析)裝入類(lèi)。當編寫(xiě)我們自己的 loadClass 時(shí),可以調用 resolveClass,這取決于 loadClass 的 resolve 參數的值。
方法 findLoadedClass
findLoadedClass 充當一個(gè)緩存:當請求 loadClass 裝入類(lèi)時(shí),它調用該方法來(lái)查看 ClassLoader 是否已裝入這個(gè)類(lèi),這樣可以避免重新裝入已存在類(lèi)所造成的麻煩。應首先調用該方法。
組裝
讓我們看一下如何組裝所有方法。
我們的 loadClass 實(shí)現示例執行以下步驟。(這里,我們沒(méi)有指定生成類(lèi)文件是采用了哪種技術(shù) -- 它可以是從 Net 上裝入、或者從歸檔文件中提取、或者實(shí)時(shí)編譯。無(wú)論是哪一種,那是種特殊的神奇方式,使我們獲得了原始類(lèi)文件字節。)
findLoadedClass 來(lái)查看是否存在已裝入的類(lèi)。defineClass 將它們轉換成 Class 對象。findSystemClass 查看是否從本地文件系統獲取類(lèi)。resolve 參數是 true,那么調用 resolveClass 解析 Class 對象。ClassNotFoundException。
第三章:Compiling ClassLoader
CCL 揭密
我們的 ClassLoader (CCL) 的任務(wù)是確保代碼被編譯和更新。
下面描述了它的工作方式:
ClassNotFoundException。findSystemClass 來(lái)尋找該類(lèi)。ClassNotFoundException。
在深入討論之前,應該先退一步,討論 Java 編譯。通常,Java 編譯器不只是編譯您要求它編譯的類(lèi)。它還會(huì )編譯其它類(lèi),如果這些類(lèi)是您要求編譯的類(lèi)所需要的類(lèi)。
CCL 逐個(gè)編譯應用程序中的需要編譯的每一個(gè)類(lèi)。但一般來(lái)說(shuō),在編譯器編譯完第一個(gè)類(lèi)后,CCL 會(huì )查找所有需要編譯的類(lèi),然后編譯它。為什么?Java 編譯器類(lèi)似于我們正在使用的規則:如果類(lèi)不存在,或者與它的源碼相比,它比較舊,那么它需要編譯。其實(shí),Java 編譯器在 CCL 之前的一個(gè)步驟,它會(huì )做大部分的工作。
當 CCL 編譯它們時(shí),會(huì )報告它正在編譯哪個(gè)應用程序上的類(lèi)。在大多數的情況下,CCL 會(huì )在程序中的主類(lèi)上調用編譯器,它會(huì )做完所有要做的 -- 編譯器的單一調用已足夠了。
然而,有一種情形,在第一步時(shí)不會(huì )編譯某些類(lèi)。如果使用 Class.forName 方法,通過(guò)名稱(chēng)來(lái)裝入類(lèi),Java 編譯器會(huì )不知道這個(gè)類(lèi)時(shí)所需要的。在這種情況下,您會(huì )看到 CCL 再次運行 Java 編譯器來(lái)編譯這個(gè)類(lèi)。在源代碼中演示了這個(gè)過(guò)程。
使用 CompilationClassLoader
要使用 CCL,必須以特殊方式調用程序。不能直接運行該程序,如:
% java Foo arg1 arg2
應以下列方式運行它:
% java CCLRun Foo arg1 arg2
CCLRun 是一個(gè)特殊的存根程序,它創(chuàng )建 CompilingClassLoader 并用它來(lái)裝入程序的主類(lèi),以確保通過(guò) CompilingClassLoader 來(lái)裝入整個(gè)程序。CCLRun 使用 Java Reflection API 來(lái)調用特定類(lèi)的主方法并把參數傳遞給它。有關(guān)詳細信息,請參閱源代碼。
運行示例
源碼包括了一組小類(lèi),它們演示了工作方式。主程序是 每個(gè)類(lèi)都聲明已被裝入并運行?,F在用源代碼來(lái)試一下。編譯 CCLRun 和 CompilingClassLoader。確保不要編譯其它類(lèi)( 請注意,首先調用編譯器,Foo 類(lèi),它創(chuàng )建類(lèi) Bar 的實(shí)例。類(lèi) Bar 創(chuàng )建另一個(gè)類(lèi) Baz 的實(shí)例,它在 baz 包內,這是為了展示 CCL 是如何處理子包里的代碼。Bar 也是通過(guò)名稱(chēng)裝入的,其名稱(chēng)為 Boo,這用來(lái)展示它也能與 CCL 工作。 Foo、Bar、Baz 和 Boo),否則將不會(huì )使用 CCL,因為這些類(lèi)已經(jīng)編譯過(guò)了。
% 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 通過(guò)名稱(chēng)來(lái)裝入 Boo 時(shí),被調用它,這時(shí) CCL 會(huì )再次調用編譯器來(lái)編譯它。
--------------------------------------------------------------------------------------
第四章:java2 中ClassLoader的變動(dòng)
概述
在 Java 版本 1.2 和以后的版本中,對 ClassLoader 做了一些改進(jìn)。任何為老系統編寫(xiě)的代碼可以在新版本中運行,但新系統為您提供了一些便利。
新模型是委托模型,這意味著(zhù)如果 ClassLoader 不能找到類(lèi),它會(huì )請求父代 ClassLoader 來(lái)執行此項任務(wù)。所有 ClassLoaders 的根是系統 ClassLoader,它會(huì )以缺省方式裝入類(lèi) -- 即,從本地文件系統。
loadClass 的缺省實(shí)現
定制編寫(xiě)的 loadClass 方法一般嘗試幾種方式來(lái)裝入所請求的類(lèi),如果您編寫(xiě)許多類(lèi),會(huì )發(fā)現一次次地在相同的、很復雜的方法上編寫(xiě)變量。
在 Java 1.2 中 loadClass 的實(shí)現嵌入了大多數查找類(lèi)的一般方法,并使您通過(guò)覆蓋 findClass 方法來(lái)定制它,在適當的時(shí)候 findClass 會(huì )調用 loadClass。
這種方式的好處是您可能不一定要覆蓋 loadClass;只要覆蓋 findClass 就行了,這減少了工作量。
新方法:findClass
loadClass 的缺省實(shí)現調用這個(gè)新方法。findClass 的用途包含您的 ClassLoader 的所有特殊代碼,而無(wú)需要復制其它代碼(例如,當專(zhuān)門(mén)的方法失敗時(shí),調用系統 ClassLoader)。
新方法:getSystemClassLoader
如果覆蓋 findClass 或 loadClass,getSystemClassLoader 使您能以實(shí)際 ClassLoader 對象來(lái)訪(fǎng)問(wèn)系統 ClassLoader(而不是固定的從 findSystemClass 調用它)。
新方法:getParent
為了將類(lèi)請求委托給父代 ClassLoader,這個(gè)新方法允許 ClassLoader 獲取它的父代 ClassLoader。當使用特殊方法,定制的 ClassLoader 不能找到類(lèi)時(shí),可以使用這種方法。 父代 ClassLoader 被定義成創(chuàng )建該 ClassLoader 所包含代碼的對象的 ClassLoader。 ----------------------------------------------------------------------------------
第五章.源代碼
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!" );
}
}
聯(lián)系客服