最近做项目需要自己写类加载器加载指定压缩包中的类(jar包),基本思路是得到指定的压缩包并通过java.util.jar包中的类访问其中的资源,找到指定的类名所对应的.class文件,得到其输入流,通过其输入流读取其中的字节并以字节数组的形式返回,通过ClassLoader中的方法加载此字节数组所代表的类.初始想法的代码如下(暂不考虑异常情况):

/**
 *自定义加载器,加载指定的压缩包中的类
*/
public class ComponentLoader extends ClassLoader{
  public  Class findClass(String className)throws ClassNotFoundException{
    try{
      String pos = "/*用户可指定压缩包的位置*/";
      File jarFile = new File(pos);
      InputStream is = new JarReader().getClassStream(className,jarFile);
      if(is != null){
        //得到输入流的可读长度(即文件的长度)并构造一个存放文件的字节数组,读入流
        byte[] buf = new byte[is.available()];
        is.read(buf,0,buf.length);
        is.close();
        //得到文件的字节数组后调用ClassLoader的方法将字节数组加载为类
         return defineClass(className,buf,0,buf.length);
      }
    }catch(IOException e){
       e.printStackTrace();
    }
     return super.findClass(className);
  }
}

/**
 *压缩包的管理器,用于将指定压缩包中的相关字节码文件以输入流返回
*/
public class JarReader{
  public InputStream getClassStream(String cname,File jarFile) throws Exception{
    //将类名转换为文件名
    String classFile = cname.replace(".","/").concat(".class");
    JarFile jf = new JarFile(jarFile);
    //得到代表字节码文件的条目
    JarEntry entry = (JarEntry)jf.getEntry(classFile);
    if(entry != null){
      return jf.getInputStream(entry);
    }
    return null;
  }
}

运行上述代码加载类发现随着类的不同有时成功有时报错,虽然错误描述不一样,但都是java.lang.ClassFormatError,总是出现这种情况,弄的我头都大了,上网找资料,开始以为是JDK的版本问题,于是把Eclipse的版本全部都调为一致的1.5版,后来自己用文本编辑器写代码,用最原始的方法编译并执行代码,发现从本地文件中读取.class字节码文件没有问题,一旦将文件打包再从包中读取就会出现同样的错误提示.后无意中发现当输入流的read(byte[],int,int)方法并不是一次性的读取指定长度的字节流,而是不确定的,尤其是在压缩文件中读取,经测试,我的压缩包中的字节码文件超过900字节就会报错,小于就没问题,所以应该是输入流的读取出现问题,遂调整思路如下:先得到字节码文件的总长度,一般情况下InputStream.available()方法和ZipEntry.getSize()方法都可得到,可以的话建议使用后者.通过得到的长度构造一个存放完整文件的字节数组,然后声明一个缓冲数组用来每次读取输入流中的数据,得到读取的长度并将读到的字节拷贝到前者数组中,最后通过完整文件的数组加载字节码文件,做此工作只需将第一个类的部分代码修改如下:

/**
 *自定义加载器,加载指定的压缩包中的类
*/
public class ComponentLoader extends ClassLoader{
  public  Class findClass(String className)throws ClassNotFoundException{
    try{
      String pos = "/*用户可指定压缩包的位置*/";
      File jarFile = new File(pos);
      InputStream is = new JarReader().getClassStream(className,jarFile);

      //********************修改后**********************
      if(is != null){
        //得到输入流的可读长度(即文件的长度)并构造一个存放文件的字节数组
         int length = is.available();
        if(length > 0){
          //存放完整文件的字节数组
           byte[] listBuf = new byte[length];
          //缓冲数组,大小可自行指定
           byte[] buf = new byte[1024];
          //记录存放文件的数组的当前插入位置
           int currentPos = 0;
          //记录每次读取的长度
           int count;
          while((count = is.read(buf,0,buf.length) != -1){
           //将读取的内容拷贝到listBuf数组中
             System.arraycopy(buf,0,listBuf,currentPos,count);
           //将当前插入位置重置
             currentPos += count;
          }
          is.close();
          //通过完整的字节数组加载类
          return defineClass(className,listBuf,0,buf.length);
        }
      }
      //***********************************************

    }catch(IOException e){
       e.printStackTrace();
    }
     return super.findClass(className);
  }
}

此时我的代码可以正确的加载类了.