最近做项目需要自己写类加载器加载指定压缩包中的类(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);
}
}
此时我的代码可以正确的加载类了.