您的位置:首页 > 其它

如何实现自定义的ClassLoader

2004-07-05 22:02 369 查看
作者: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很简单,一般只需要很少的几个步骤就可以完成。
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 = findBootstrapClass(name);
11:     }
12:    }catch(ClassNotFoundException e){
13:     // 如果仍然没有找到,那么就调用findclass查找这个类.      
14:     c = findClass(name);//sarsor's note:findClass() may throw
                                          //ClassNotFoundException why not try-catch block here

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 = getClassLoader();
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文件等),以满足你自己的口味。

ClassLoader的补充说明

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)。
(sarsor注:做了若干的修改,改正了文章中几个明显的错误,如果本人改错了,那非常抱歉)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息