首先我们定义一个接口,使所有承它的类都必d现actionҎQ如下:
public interface ActionInterface {
public String action();
}
完成后将其打包ؓtestInterface.jar文g?/p>
接下来新Z工程Qؓ了编译通过Q引入之前打好的testInterface.jar包。ƈ创徏TestActionc,使它实现ActionInterface接口。如下:
public class TestAction implements ActionInterface {
public String action() {
return " com.mxjava.TestAction.action " ;
}
}
完成后将其打包ؓtest.jarQ放在c盘根目录下。下面要做的是利用URLClassLoader加蝲q运行TestAction的actionҎQƈ返回的值打印在控制C?/p>
新徏一工程Q引入testInterface.jar包。ƈ创徏一可执行类QmainҎQ,在其中加入如下代码:
URL url = new URL(“file:C: / test.jar”);
URLClassLoader myClassLoader = new URLClassLoader( new URL[] { url } );
Class myClass = myClassLoader.loadClass(“com.mxjava.TestAction”);
ActionInterface action = (ActionInterface)myClass.newInstance();
System.out.println(action.action());
在上面的例子中,首先利用URLClassLoader加蝲了C:\test.jar包,其中的com.mxjava.TestActionc蝲入内存,其强制转型为testInterface包中的ActionInterfacecdQ最后调用其actionҎQƈ打印到控制台中?/p>
执行E序后,在控制台上如期打印出我们惌的内宏V但是,事情q没有那么简单,当我们将该代码移动web应用中时Q就会抛出异常。原来,Java为我们提供了三种可选择的ClassLoaderQ?br />
1. pȝcd载器或叫作应用类加蝲?(system classloader or application classloader)
2. 当前cd载器
3. 当前U程cd载器
在上例中我们使用javac命o来运行该E序Q这时候用的是系l类加蝲?(system classloader)。这个类加蝲器处?-classpath下的cd载工作,可以通过ClassLoader.getSystemClassLoader()Ҏ调用?ClassLoader 下所有的 getSystemXXX()的静态方法都是通过q个Ҏ定义的。在代码中,应该量地调用q个ҎQ以其它的类加蝲器作Z理。否则代码将只能工作在简单的命o行应用中。当在web应用中时Q服务器也是利用ClassLoader来加载class的,׃ClassLoader的不同,所以在强制转型时JVM认定不是同一cd。(在JAVA中,一个类用其完全匚wcd(fully qualified class name)作ؓ标识Q这里指的完全匹配类名包括包名和cd。但在JVM中一个类用其全名和一个加载类ClassLoader的实例作为唯一标识。因此,如果一个名为Pg的包中,有一个名为Cl的类Q被cd载器KlassLoader的一个实例kl1加蝲QCl的实例,即C1.class在JVM中表CZؓ(Cl, Pg, kl1)。这意味着两个cd载器的实?Cl, Pg, kl1) ?(Cl, Pg, kl2)是不同的Q被它们所加蝲的类也因此完全不同,互不兼容的。)Z能够使程序正运行,我们首要解决的问题就是,如何URLClassLoader加蝲的类Q同当前ClassLoader保持在同一cd载器中。解x法很单,利用java提供的第三种ClassLoader—当前线E类加蝲器即可。jdk api文׃发现QURLClassLoader提供了三U构造方式:
// 使用默认的委托父 ClassLoader 为指定的 URL 构造一个新 URLClassLoader?nbsp;
URLClassLoader(URL[] urls)
// 为给定的 URL 构造新 URLClassLoader?nbsp;
URLClassLoader(URL[] urls, ClassLoader parent)
// 为指定的 URL、父cd载器?URLStreamHandlerFactory 创徏?URLClassLoader?
URLClassLoader(URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory)
接下来要做的是Q在构造URLClassLoaderӞ当前线E类加蝲器置入即可。如下:
URLClassLoader myClassLoader = new URLClassLoader( new URL[] { url } , Thread.currentThread().getContextClassLoader());
ȝQ?br />
Java是利用ClassLoader来加载类到内存的QClassLoader本n是用java语言写的Q所以我们可以扩展自qClassLoader。利用URLClassLoader可以加蝲指定jar包中的类到内存。在命行上利用URLClassLoader加蝲jarӞ是用系l类加蝲器来加蝲class的,所以在web环境下,׃出错。这是因为JVM中一个类用其全名和一个加载类ClassLoader的实例作为唯一标识的。我们只要利用URLClassLoader的第二种构造方法ƈ传入当前U程cd载器卛_解决?/p>
在流行的商业化编E语a中,Java 语言׃?Java 虚拟?(JVM) 上运行而显得与众不同。这意味着已编译的E序是一U特D的、独立于q_的格式,q依赖于它们所q行的机器。在很大E度上,q种格式不同于传l的可执行程序格式?
?C ?C++ ~写的程序不同,Java E序q不是一个可执行文gQ而是p多独立的cL件组成,每一个文件对应于一?Java cR?
此外Q这些类文gq立即全部都装入内存,而是ҎE序需要装入内存。ClassLoader ?JVM 中将c装入内存的那部分?
而且QJava ClassLoader 是?Java 语言~写的。这意味着创徏您自q ClassLoader 非常ҎQ不必了?JVM 的微细节?
如果 JVM 已经有一?ClassLoaderQ那么ؓ什么还要编写另一个呢Q问得好。缺省的 ClassLoader 只知道如何从本地文gpȝ装入cL件。不q这只适合于常规情况,卛_全部~译?Java E序Qƈ且计机处于{待状态?
?Java 语言最h意的事就?JVM 可以非常ҎC那些非本地硬盘或从网l上获取cR例如,览者可以用定制的 ClassLoader ?Web 站点装入可执行内宏V?
有许多其它方式可以获取类文g。除了简单地从本地或|络装入文g以外Q可以用定制的 ClassLoader 完成以下dQ?
定制 ClassLoader CZ
如果使用q?JDK 或Q何基?Java 览器中?Applet 查看器,那么您差不多肯定使用q定制的 ClassLoader? Sun 最初发?Java 语言Ӟ其中最令h兴奋的一件事是观看这Ҏ技术是如何执行在运行时从远E的 Web 服务器装入的代码。(此外Q还有更令h兴奋的事 -- Java 技术提供了一U便于编写代码的强大语言。)更一些o人激动的是它可以执行从远E?Web 服务器通过 HTTP q接发送过来的字节码? 此项功能归功?Java 语言可以安装定制 ClassLoader。Applet 查看器包含一?ClassLoaderQ它不在本地文gpȝ中寻扄Q而是讉Kq程服务器上?Web 站点Q经q?HTTP 装入原始的字节码文gQƈ把它们{换成 JVM 内的cR? 览器和 Applet 查看器中?ClassLoaders q可以做其它事情Q它们支持安全性以及不同?Applet 在不同的面上运行而互不干扰? Luke Gorrie ~写?Echidna 是一个开放源码包Q它可以使您在单个虚拟机上运行多?Java 应用E序。(请参?a >q一步了解和参考资?/a>。)它用定制的 ClassLoaderQ通过向每个应用程序提供该cL件的自n副本Q以防止应用E序互相q扰? 我们?ClassLoader CZ
了解?ClassLoader 如何工作以及如何~写 ClassLoader 之后Q我们将创徏UC CompilingClassLoader (CCL) ?Classloader。CCL 为我们编?Java 代码Q而无需要我们干涉这个过E。它基本上就cM于直接构建到q行时系l中?"make" E序? 注:q一步了解之前,应注意在 JDK 版本 1.2 中已改进?ClassLoader pȝ的某些方面(?Java 2 q_Q。本教程是按 JDK 版本 1.0 ?1.1 写的Q但也可以在以后的版本中q行?/p>
Java 2 ?ClassLoader 的变?/a>描述?Java 版本 1.2 中的变动Qƈ提供了一些详l信息,以便修改 ClassLoader 来利用这些变动?
------------------------------------------------------------------------------------------------------
ClassLoader 的基本目标是对类的请求提供服务。当 JVM 需要用类Ӟ它根据名U向 ClassLoader hq个c,然后 ClassLoader 试图q回一个表C个类?Class
对象?
通过覆盖对应于这个过E不同阶D늚ҎQ可以创建定制的 ClassLoader?
在本章的其余部分Q您会学?Java ClassLoader 的关键方法。您了解每一个方法的作用以及它是如何适合装入cL件这个过E的。您也会知道Q创q ClassLoader Ӟ需要编写什么代码?
在下一章中Q您会利用q些知识来用我们的 ClassLoader CZ -- CompilingClassLoader?
Ҏ loadClass
?Java 版本 1.1 和以前的版本中, Ҏ
Ҏ
对于定制?ClassLoaderQ只有在试其它Ҏ装入cM后,再? 其工作流E如下: 在大多数定制 ClassLoaders 中,首先调用 Ҏ 正如前面所提到的,可以不完全地Q不带解析)装入c,也可以完全地Q带解析Q装入类。当~写我们自己? Ҏ l装 ClassLoader.loadClass()
?ClassLoader 的入口点。其特征如下Q?Class loadClass( String name, boolean resolve );
name
参数指定?JVM 需要的cȝ名称Q该名称以包表示法表C,?Foo
?java.lang.Object
?resolve
参数告诉Ҏ是否需要解析类。在准备执行cM前,应考虑c解析。ƈ不L需要解析。如?JVM 只需要知道该cL否存在或扑և该类的超c,那么׃需要解析?loadClass
Ҏ是创建定制的 ClassLoader 时唯一需要覆盖的Ҏ。(Java 2 ?ClassLoader 的变?/a>提供了关?Java 1.2 ?findClass()
Ҏ的信息。)
defineClass
defineClass
Ҏ?ClassLoader 的主要诀H。该Ҏ接受由原始字节组成的数组q把它{换成 Class
对象。原始数l包含如从文件系l或|络装入的数据?defineClass
理 JVM 的许多复杂、神U和倚赖于实现的斚w -- 它把字节码分析成q行时数据结构、校验有效性等{。不必担心,您无需亲自~写它。事实上Q即使您惌q么做也不能覆盖它,因ؓ它已被标记成最l的?
findSystemClass
findSystemClass
Ҏ从本地文件系l装入文件。它在本地文件系l中LcLӞ如果存在Q就使用 defineClass
原始字节{换成 Class
对象Q以该文g转换成类。当q行 Java 应用E序Ӟq是 JVM 正常装入cȝ~省机制。(Java 2 ?ClassLoader 的变?/a>提供了关?Java 版本 1.2 q个q程变动的详l信息。) findSystemClass
。原因很单:ClassLoader 是负责执行装入类的特D步骤,不是负责所?/em>cR例如,即 ClassLoader 从远E的 Web 站点装入了某些类Q仍焉要在本地机器上装入大量的基本 Java 库。而这些类不是我们所兛_的,所以要 JVM 以缺省方式装入它们:从本地文件系l。这是 findSystemClass
的用途?
findSystemClass
Q它从文gpȝ装入该类?findSystemClass
以节省在本地可以装入的许多 Java 库类而要在远E?Web 站点上查找所q旉。然而,正如Q在下一章节所看到的,直到信能自动编译我们的应用E序代码Ӟ才让 JVM 从本地文件系l装入类?
resolveClass
loadClass
Ӟ可以调用 resolveClass
Q这取决?loadClass
?resolve
参数的倹{?/font>findLoadedClass
findLoadedClass
充当一个缓存:当请?loadClass
装入cLQ它调用该方法来查看 ClassLoader 是否已装入这个类Q这样可以避免重新装入已存在cL造成的麻烦。应首先调用该方法?/font>
让我们看一下如何组装所有方法?
我们?loadClass
实现CZ执行以下步骤。(q里Q我们没有指定生成类文g是采用了哪种技?-- 它可以是?Net 上装入、或者从归文g中提取、或者实时编译。无论是哪一U,那是U特D的奇方式Q我们获得了原始类文g字节。)
findLoadedClass
来查看是否存在已装入的类?br x="7" />
defineClass
它们{换成 Class
对象?br x="7" />
findSystemClass
查看是否从本地文件系l获取类?br x="7" />
resolve
参数?true
Q那么调?resolveClass
解析 Class
对象?br x="7" />
ClassNotFoundException
?br x="7" />
W三章:Compiling ClassLoader CCL 揭密
我们?ClassLoader (CCL) 的Q务是保代码被编译和更新? 下面描述了它的工作方式:
在深入讨Z前,应该先退一步,讨论 Java ~译。通常QJava ~译器不只是~译您要求它~译的类。它q会~译其它c,如果q些cL您要求编译的cL需要的cR? CCL 逐个~译应用E序中的需要编译的每一个类。但一般来_在编译器~译完第一个类后,CCL 会查找所有需要编译的c,然后~译它。ؓ什么?Java ~译器类g我们正在使用的规则:如果cM存在Q或者与它的源码相比Q它比较旧,那么它需要编译。其实,Java ~译器在 CCL 之前的一个步骤,它会做大部分的工作? ?CCL ~译它们Ӟ会报告它正在~译哪个应用E序上的cR在大多数的情况下,CCL 会在E序中的ȝ上调用编译器Q它会做完所有要做的 -- ~译器的单一调用已够了? 然而,有一U情形,在第一步时不会~译某些cR如果? 使用 CompilationClassLoader
要?CCLQ必MҎ方式调用E序。不能直接运行该E序Q如Q? 应以下列方式q行它: CCLRun 是一个特D的存根E序Q它创徏 CompilingClassLoader q用它来装入E序的主c,以确保通过 CompilingClassLoader 来装入整个程序。CCLRun 使用 Java Reflection API 来调用特定类的主Ҏq把参数传递给它。有兌l信息,请参?a >源代?/a>? q行CZ
源码包括了一l小c,它们演示了工作方式。主E序? h意,首先调用~译器, -------------------------------------------------------------------------------------- W四章:java2 中ClassLoader的变?/strong> 概述
?Java 版本 1.2 和以后的版本中,?ClassLoader 做了一些改q。Q何ؓ老系l编写的代码可以在新版本中运行,但新pȝ为您提供了一些便利? 新模型是委托模型Q这意味着如果 ClassLoader 不能扑ֈc,它会h父代 ClassLoader 来执行此Q务。所?ClassLoaders 的根是系l?ClassLoaderQ它会以~省方式装入c?-- 卻I从本地文件系l?
定制~写? ?Java 1.2 ? q种方式的好处是您可能不一定要覆盖 新方法: 新方法: 如果覆盖 新方法:
Z类h委托l父?ClassLoaderQ这个新Ҏ允许 ClassLoader 获取它的父代 ClassLoader。当使用ҎҎQ定制的 ClassLoader 不能扑ֈcLQ可以用这U方法? 父代 ClassLoader 被定义成创徏?ClassLoader 所包含代码的对象的 ClassLoader? ---------------------------------------------------------------------------------- W五?源代?/strong>
Java ~译的工作方?/strong>
ClassNotFoundException
?br x="7" />
findSystemClass
来寻找该cR?br x="7" />
ClassNotFoundException
?br x="7" />
Class.forName
ҎQ通过名称来装入类QJava ~译器会不知道这个类时所需要的。在q种情况下,您会看到 CCL 再次q行 Java ~译器来~译q个cR在源代?/a>中演CZq个q程?
% java Foo arg1 arg2
% java CCLRun Foo arg1 arg2
Foo
c,它创建类 Bar
的实例。类 Bar
创徏另一个类 Baz
的实例,它在 baz
包内Q这是ؓ了展C?CCL 是如何处理子包里的代码?code style="font-size: 12px; font-family: Courier New,Courier,monospace">Bar 也是通过名称装入的,其名UCؓ Boo
Q这用来展示它也能与 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
Ӟ被调用它Q这?CCL 会再ơ调用编译器来编译它?
loadClass
的缺省实?/strong>loadClass
Ҏ一般尝试几U方式来装入所h的类Q如果您~写许多c,会发Cơ次地在相同的、很复杂的方法上~写变量?loadClass
的实现嵌入了大多数查扄的一般方法,q您通过覆盖 findClass
Ҏ来定制它Q在适当的时?findClass
会调?loadClass
?loadClass
Q只要覆?findClass
p了,q减了工作量?
findClass
loadClass
的缺省实现调用这个新Ҏ?code style="font-size: 12px; font-family: Courier New,Courier,monospace">findClass 的用途包含您?ClassLoader 的所有特D代码,而无需要复制其它代码(例如Q当专门的方法失败时Q调用系l?ClassLoaderQ?/font>getSystemClassLoader
findClass
?loadClass
Q?code style="font-size: 12px; font-family: Courier New,Courier,monospace">getSystemClassLoader 使您能以实际 ClassLoader
对象来访问系l?ClassLoaderQ而不是固定的?findSystemClass
调用它)?/font>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!" );
}
}