一个简单的自定义ClassLoader的实现

时间:2021-09-09 16:10:22
一个简单的自定义ClassLoader的实现
kert 原创  (参与分:759,专家分:960)   发表:2002-7-18 下午5:55   更新:2002-7-18 下午8:05   版本:1.0   阅读:9737

很多时候人们会使用一些自定义的ClassLoader ,而不是使用系统的Class Loader。大多数时候人们这样做的原因是,他们在编译时无法预知运行时会需要那些Class。特别是在那些appserver中,比如tomcat,Avalon-phonix,Jboss中。或是程序提供一些plug-in的功能,用户可以在程序编译好之后再添加自己的功能,比如ant, jxta-shell等。定制一个ClassLoader很简单,一般只需要理解很少的几个方法就可以完成。
一个最简单的自定义的ClassLoader从ClassLoader类继承而来。这里我们要做一个可以在运行时指定路径,加载这个路径下的class的ClassLoader。
通常我们使用ClassLoader.loadClass(String):Class方法,通过给出一个类名,就会得到一个相应的Class实例。因此只要小小的改动这个方法,就可以实现我们的愿望了。
源码:
  1. protected synchronized Class loadClass(String name, boolean resolve)    throws ClassNotFoundException    {    
  2.     // First, check if the class has already been loaded    
  3.     Class c = findLoadedClass(name);    
  4.     if (c == null) {
  5.         try {
  6.            if (parent != null) {
  7.               c = parent.loadClass(name, false);
  8.            }else{
  9.               c = findBootstrapClass0(name);
  10.            }
  11.         }catch(ClassNotFoundException e){
  12.             // If still not found, then call findClass in order
  13.             // to find the class.            
  14.            c = findClass(name);
  15.         }
  16.     }
  17.     if (resolve) {
  18.       resolveClass(c);
  19.     }
  20.     return c;
  21. }

Source from ClassLoader.java

First,check JavaAPI doc:上面指出了缺省的loadClass方法所做的几个步骤。
1.    调用findLoadedClass(String):Class 检查一下这个class是否已经被加载过了,由于JVM 规范规定ClassLoader可以cache它所加载的Class,因此如果一个class已经被加载过的话,直接从cache中获取即可。
2.    调用它的parent 的loadClass()方法,如果parent为空,这使用JVM内部的class loader(即著名的bootstrap classloader)。
3.    如果上面两步都没有找到,调用findClass(String)方法来查找并加载这个class。
后面还有一句话,在Java 1.2版本以后,鼓励用户通过继承findClass(String)方法实现自己的class loader而不是继承loadClass(String)方法。
既然如此,那么我们就先这么做:)
  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, String baseDir) {
  5.            super(parent);
  6.            this.baseDir = baseDir;
  7.     }
  8.     protected Class findClass(String name)
  9.             throws ClassNotFoundException {
  10.         LOG.debug("findClass " + name);
  11.         byte[] bytes = loadClassBytes(name);
  12.         Class theClass = defineClass(name, bytes, 0, bytes.length);//A
  13.         if (theClass == null)
  14.             throw new ClassFormatError();
  15.         return theClass;
  16.     }
  17.     private byte[] loadClassBytes(String className) throws
  18.         ClassNotFoundException {
  19.         try {
  20.             String classFile = getClassFile(className);
  21.             FileInputStream fis = new FileInputStream(classFile);
  22.             FileChannel fileC = fis.getChannel();
  23.             ByteArrayOutputStream baos = new ByteArrayOutputStream();
  24.             WritableByteChannel outC = Channels.newChannel(baos);
  25.             ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
  26.             while (true) {
  27.                 int i = fileC.read(buffer);
  28.                 if (i == 0 || i == -1) {
  29.                     break;
  30.                 }
  31.                 buffer.flip();
  32.                 outC.write(buffer);
  33.                 buffer.clear();
  34.             }
  35.             fis.close();
  36.             return baos.toByteArray();
  37.         } catch (IOException fnfe) {
  38.             throw new ClassNotFoundException(className);
  39.         }
  40.     }
  41.     private String getClassFile(String name) {
  42.         StringBuffer sb = new StringBuffer(baseDir);
  43.         name = name.replace('.', File.separatorChar) + ".class";
  44.         sb.append(File.separator + name);
  45.         return sb.toString();
  46.     }
  47. }

[i]Ps:这里使用了一些JDK1.4的nio的代码:)[/i]
很简单的代码,关键的地方就在A处,我们使用了defineClass方法,目的在于把从class文件中得到的二进制数组转换为相应的Class实例。defineClass是一个native的方法,它替我们识别class文件格式,分析读取相应的数据结构,并生成一个class实例。

还没完呢,我们只是找到了发布在某个目录下的class,还有资源呢。我们有时会用Class.getResource():URL来获取相应的资源文件。如果仅仅使用上面的ClassLoader是找不到这个资源的,相应的返回值为null。

同样我们看一下原来的ClassLoader内部的结构。
  1. public java.net.URL getResource(String name) {
  2.         name = resolveName(name);
  3.         ClassLoader cl = getClassLoader0();//这里
  4.         if (cl==null) {
  5.             // A system class.
  6.             return ClassLoader.getSystemResource(name);
  7.         }
  8.         return cl.getResource(name);}


原来是使用加载这个class的那个classLoader获取得资源。

  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. }




这样看来只要继承findResource(String)方法就可以了。修改以下我们的代码:

  1. //新增的一个findResource方法
  2. protected URL findResource(String name) {
  3.         LOG.debug("findResource " + name);
  4.         try {
  5.             URL url = super.findResource(name);
  6.             if (url != null)
  7.                 return url;
  8.             url = new URL("file:///" + converName(name));
  9.             //简化处理,所有资源从文件系统中获取
  10.             return url;
  11.         } catch (MalformedURLException mue) {
  12.             LOG.error("findResource", mue);
  13.             return null;
  14.         }
  15. }
  16. private String converName(String name) {
  17.         StringBuffer sb = new StringBuffer(baseDir);
  18.         name = name.replace('.', File.separatorChar);
  19.         sb.append(File.separator + name);
  20.         return sb.toString();
  21. }


好了,到这里一个简单的自定义的ClassLoader就做好了,你可以添加其他的调料(比如安全检查,修改class文件等),以满足你自己的口味:)

----------------
I am not guru,but nonsenser

版权声明   给作者写信
本篇文章对您是否有帮助?  投票:         投票结果:   一个简单的自定义ClassLoader的实现  9      一个简单的自定义ClassLoader的实现  0
作者其它文章: 作者全部文章
  评论人:efly    参与分: 1044    专家分: 1820 发表时间: 2002-12-5 上午11:04
有一个很好的方法建议使用:resolveClass。这个方法把Class载入ClassLoader,供以后调用,或其它类的调用。