private void readJAR(JarFile jar) throws IOException {
Enumeration<JarEntry> en = jar.entries();
while (en.hasMoreElements()) {
JarEntry je = en.nextElement();
je.getName();
String name = je.getName();
if (name.endsWith(".class")) {
//String className = name.replace(File.separator, ".").replace(".class", "");
String className = name.replace("\", ".")
.replace("/", ".")
.replace(".class", "");
InputStream input = null;
ByteArrayOutputStream baos = null;
try {
input = jar.getInputStream(je);
baos = new ByteArrayOutputStream();
int bufferSize = 1024;
byte[] buffer = new byte[bufferSize];
int bytesNumRead = 0;
while ((bytesNumRead = input.read(buffer)) != -1) {
baos.write(buffer, 0, bytesNumRead);
}
addClass(className, baos.toByteArray());
} catch (Exception e) {
e.printStackTrace();
} finally {
if (baos != null) {
baos.close();
}
if (input != null) {
input.close();
}
}
}
}
}
private void scanJarFile(File file) {
if (file.exists()) {
if (file.isFile() && file.getName().endsWith(".jar")) {
try {
readJAR(new JarFile(file));
} catch (IOException e) {
e.printStackTrace();
}
} else if (file.isDirectory()) {
for (File f : file.listFiles()) {
scanJarFile(f);
}
}
}
}
public void addJar(String jarPath) throws IOException {
File file = new File(jarPath);
if (file.exists()) {
JarFile jar = new JarFile(file);
readJAR(jar);
}
}
}
如何使用的代码就不贴了,和3.2节自定义类加载器的使用方式一样。只是构造方法的参数变成classPath了,篇末有代码。当创建MyClassLoader对象时,会自动添加指定classPath下面的所有class和jar里面的class到classMap中,classMap维护className和classCode字节码的关系,只是个缓冲作用,避免每次都从文件中读取。自定义类加载器每次loadClass都会首先在JVM中找是否已经加载className的类,如果不存在就会到classMap中取,如果取不到就是加载错误了。
六、最后
至此,JVM自定义类加载器加载指定classPath下的所有class及jar已经完成了。这篇博文花了两天才写完,在写的过程中有意识地去了解了许多代码的细节,收获也很多。本来最近仅仅是想实现Quartz控制台页面任务添加支持动态class,结果不知不觉跑到类加载器的坑了,在此也趁这个机会总结一遍。当然以上内容并不能保证正确,所以希望大家看到错误能够指出,帮助我更正已有的认知,共同进步。。。