作者:Kert 本文选自:开放系统世界——赛迪网 2002年09月23日
ClassLoader,顾名思义是用来Load Class的,即加载Java类。ClassLoader读入一个字节数组,并且经过处理返回一个JVM内部可以识别的Class实例。Java虚拟机使 用一套复杂但有效的方式来进行这一个至关重要的过程处理,并且提供了许多灵活的方式供人们扩展这套机制。
为什么要使用自定义ClassLoader
很 多时候人们会选择使用自定义的ClassLoader,而不使用系统的ClassLoader。这样做的原因是,在编译时无法预知运行时会需要哪些 Class,特别是在一些AppServer中,比如Tomcat、Avalon-Phonix、Jboss;或是程序提供某种插件(Plug-In)的 特性,让用户可以在只拥有程序二进制代码的情况下添加自己的功能,比如Ant、 Jxta-shell等。
很 多时候人们会选择使用自定义的ClassLoader,而不使用系统的ClassLoader。这样做的原因是,在编译时无法预知运行时会需要哪些 Class,特别是在一些AppServer中,比如Tomcat、Avalon-Phonix、Jboss;或是程序提供某种插件(Plug-In)的 特性,让用户可以在只拥有程序二进制代码的情况下添加自己的功能,比如Ant、 Jxta-shell等。
ClassLoader内部结构
通常定制一个ClassLoader很简单,一般只需要很少的几个步骤就可以完成。
Java规范规定,所有的用户自定义ClassLoader都必须从抽象类“java.lang.ClassLoader”类继承而来。下面先看一下这个类的内部实现,以帮助我们更好的理解相关内容。
通常定制一个ClassLoader很简单,一般只需要很少的几个步骤就可以完成。
Java规范规定,所有的用户自定义ClassLoader都必须从抽象类“java.lang.ClassLoader”类继承而来。下面先看一下这个类的内部实现,以帮助我们更好的理解相关内容。
1: protected synchronized Class loadClass(String name, boolean 2: resolve)throws ClassNotFoundException{ 3: // 首先, 检查这个类是否已经加载。 4: Class c = findLoadedClass(name); 5: if (c == null) { 6: try { 7: if (parent != null) { 8: c = parent.loadClass(name, false); 9: }else{ 10: c = findBootstrapClass0(name); 11: } 12: }catch(ClassNotFoundException e){ 13: // 如果仍然没有找到,那么就调用findclass查找这个类. 14: c = findClass(name); 15: } 16: } 17: if (resolve) { 18: resolveClass(c); 19: } 20: return c; } |
通常我们使用ClassLoader.loadClass(String name):Class根据指定的类名得到一个相应的Class实例。从Java源代码中我们可以看到,缺省的ClassLoader做了如下的工作:
1. 调用FindLoadedClass(String):Class 检查一下这个Class是否已经被加载过了。由于JVM 规范规定ClassLoader可以在缓存保留它所加载的Class,因此如果一个Class已经被加载过,直接从缓存中获取即可。
2. 调用它的父类的LoadClass()方法,如果它的父类不为空,则使用JVM内部的ClassLoader(即著名的 BootstrapClassloader)来加载这个Class。在第10行我们可以看到使用了一个Native方法来调用这个Bootstrap classloader。
3. 如果上面两步都没有找到,调用FindClass(String):Class方法来查找并加载这个Class。
因此我们只要覆盖这个FindClass(String):Class方法即可达到定义ClassLoader的要求。
1: public class AnotherClassLoader extends ClassLoader { 2: private String baseDir; private static final Logger LOG = 3: Logger.getLogger(AnotherClassLoader.class); 4: public AnotherClassLoader (ClassLoader parent, 5: String baseDir) { 6: super(parent); 7: this.baseDir = baseDir; 8: } 9: protected Class findClass(String name) 10: throws ClassNotFoundException { 11: LOG.debug("findClass " + name); 12: byte[] bytes = loadClassBytes(name); 13: Class theClass = defineClass(name, bytes, 0, 14: bytes.length); 15: if (theClass == null) 16: throw new ClassFormatError(); 17: return theClass; 18: } 19: private byte[] loadClassBytes(String className) throws 20: ClassNotFoundException { 21: try { 22: String classFile = getClassFile(className); 23: FileInputStream fis = new FileInputStream(classFile); 24: FileChannel fileC = fis.getChannel(); 25: ByteArrayOutputStream baos = new 26: ByteArrayOutputStream(); 27: WritableByteChannel outC = Channels.newChannel(baos); 28: ByteBuffer buffer = ByteBuffer.allocate Direct(1024); 29: while (true) { 30: int i = fileC.read(buffer); 31: if (i == 0 || i == -1) { 32: break; 33: } 34: buffer.flip(); 35: outC.write(buffer); 36: buffer.clear(); 37: } 38: fis.close(); 39: return baos.toByteArray(); 40: } catch (IOException fnfe) { 41: throw new ClassNotFoundException(className); 42: } 43: } 44: private String getClassFile(String name){ 45: StringBuffer sb = new StringBuffer(baseDir); 46: name = name.replace('.', File.separator Char) + ".class"; 47: sb.append(File.separator + name); 48: return sb.toString(); 49: } 50: } |
以上是很简单的代码,关键的地方就在13行处。我们调用了DefineClass方法,目的在于把从文件中得到的二进制 数组转换为相应的Class实例。DefineClass是一个Native的方法,它替我们识别Class文件格式,分析、读取相应的数据结构,并生成 一个Class实例。
我们只找到了发布在某个目录下的Class,但是,如何获取相应的资源呢?我们有时会用Class.getResource()来获取相应的资源文件。如果仅仅使用上面的ClassLoader是找不到这个资源的,相应的返回值为Null。
同样我们看一下原来的Class类内部的结构。
1: public java.net.URL getResource(String name) { 2: name = resolveName(name); 3: ClassLoader cl = getClassLoader0(); 4: if (cl==null) { 5: return ClassLoader.getSystemResource(name); 6: } 7: return cl.getResource(name); 8: } |
原来系统是使用加载这个Class的ClassLoader获取资源的。那么我们再看一下ClassLoader.getResource(String)方法的实现:
1: public URL getResource(String name) { 2: URL url; 3: if (parent != null){ 4: url = parent.getResource(name); 5: }else { 6: url = getBootstrapResource(name); 7: } 8: if (url == null) { 9: url = findResource(name); 10: } 11: return url; 12: } |
同样在第9行处,JVM最后会调用一个FindResource方法获取资源的URL。因此同FindClass方法一样只要继承FindResource(String)方法就可以了。
在AnotherClassLoader中添加如下代码:
1: protected URL findResource(String name) { 2: LOG.debug("findResource " + name); 3: try { 4: URL url = super.findResource(name); 5: if (url != null) 6: return url; 7: url = new URL("file:///" + converName(name)); 8: //简化处理,所有资源从文件系统中获取 9: return url; 10: } catch (MalformedURLException mue) { 11: LOG.error("findResource", mue); 12: return null; 13: } 14:} 15:private String converName(String name) { 16: StringBuffer sb = new StringBuffer(baseDir); 17: name = name.replace('.', File.separatorChar); 18: sb.append(File.separator + name); 19: return sb.toString(); 20:} |
好了,到这里一个简单的、自定义的ClassLoader就做好了,同样你可以添加其它的调料(比如安全检查,修改class文件等),以满足你自己的口味。
1. 在Java规范中定义了两种ClassLoader,一种成为BootstrapClassLoader,另一种成为UserDefined ClassLoader。BootstrapClassLoader是属于JVM的一部分,用户不可更改,它主要负责加载JavaAPI中的Class。 UserDefinedClassLoader是用户可以接触的,包括一个用来加载CLASS_PATH定义中Class的 SystemClassLoader和用户自定义的ClassLoader。
2. 这段代码使用了Java1.4中的java.nio包下面的方法。
3. 每个Class可以通过Class.getClassLoader():ClassLoader方法获得加载它的ClassLoader。但是对于某些 JVM(包括SUN的实现)实现而言,JavaAPI中的那些Class的getClassLoader()方法返回Null(可以参阅 JavaDoc)。