这一期主要讲如何创建一个自定义类加载器以及自定义类加载器的作用。
在上一篇的类加载源码解读中说到了,AppClassLoader 和ExtClassLoader 都是集成的URLClassLoader,那么我们创建自定义类加载器最简单的方式就是 直接集成URLClassLoader 里面已经帮我们实现了LoadClass() 以及findClass(),我们只需要传入自定义的类加载路径即可
MyURLClassLoader extends URLClassLoader
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLStreamHandlerFactory;
/**
* 自定义类加载器
*/
public class MyUrlClassLoader extends URLClassLoader {
public MyUrlClassLoader(URL[] urls, ClassLoader parent) {
super(urls, parent);
}
public MyUrlClassLoader(URL[] urls) {
super(urls);
}
public MyUrlClassLoader(URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory) {
super(urls, parent, factory);
}
}
MyClassLoader extends ClassLoader
由于ClassLoader findCLass 直接抛出的 ClassNotFoundException(),所以 我们需要在自定义类里面去重写findClass()
import java.io.*;
/**
* 自定义类加载器
*/
public class MyClassLoader extends ClassLoader {
private String rootDir;
public MyClassLoader (String rootDir) {
this.rootDir = rootDir;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
// 获取类的class文件字节数组
byte[] classData = getClassData(name);
if (classData == null) {
throw new ClassNotFoundException();
} else {
//直接生成class对象
return defineClass(name, classData, 0, classData.length);
}
}
private byte[] getClassData(String className) {
// 读取类文件的字节
String path = rootDir + File.separatorChar + className.replace('.', File.separatorChar) + ".class";
try {
InputStream ins = new FileInputStream(path);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int bufferSize = 4096;
byte[] buffer = new byte[bufferSize];
int bytesNumRead = 0;
// 读取类文件的字节码
while ((bytesNumRead = ins.read(buffer)) != -1) {
baos.write(buffer, 0, bytesNumRead);
}
return baos.toByteArray();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
以上是创建一个自定义类加载器的方式,两者区别并不大。接下来我们演示下 AppClassLoader 与2个自定义类加载器, 加载同一个类的展示效果;
//自定义加载器
String myFilePath = "C:\\Users\\Administrator\\Desktop\\";
//实例化 自己的类加载器
MyClassLoader myClassLoader = new MyClassLoader(myFilePath);
Class<?> object1 = myClassLoader.loadClass("WaitLoadClass");
System.out.println("MyClassLoader加载器加载的类:"+ object1.hashCode());
URL[] urls = new URL[1];
urls[0] = new File(myFilePath).toURI().toURL();
MyUrlClassLoader myUrlClassLoader = new MyUrlClassLoader(urls);
Class<?> object3 = myUrlClassLoader.loadClass("WaitLoadClass");
System.out.println("myUrlClassLoader加载器加载的类:"+ object3.hashCode());
Class<?> object2 = ClassLoader.getSystemClassLoader().loadClass("WaitLoadClass");
System.out.println("AppClassLoader加载器加载的类:"+ object2.hashCode());
如上图 可以看出 加载一个普通的类hash code 是一样的,完全准守了双亲委派模式,那这里定义一个class loader 的 意义是什么呢? 接下来 我们把项目里的 WaitLoadClass 移动到 C:\\Users\\Administrator\\Desktop\\ 目录下 再运行一次程序,如下图可以看出,两个自定义类加载器 从桌面家加载到了class,而 AppClassLoader 提示 class not found
上面的例子 只是自定义类加载器的一个作用,指定加载项目以外的class,当然 他还有着更重要的作用,比如下载 网络的class 或者对类的加解密,以及热部署等 此处就不在详细说明。
至此 Java ClassLoader 分三篇讲完,大多是Demo 调试 很少特意介绍原理概念,主要是想通过阅读源码断点调试 来证明这些概念,有写的不对的地方 欢迎评论留言指出。