1.什么是Classloader
我们知道,我们写的java程序会被编译器编译成class文件运行在jvm虚拟机上的,Classloader就是jvm加载class文件的工具。
当我们new ClassName(),或者使用Class.forName("包路径+类名"),或者使用classloader.loadclass("包路径+类名")的时候,就会触发Classloader去类加载对应的路径去查找*.class,并创建对象。
正常情况下, 一个classLoader需要两个必要属性:
parent: 用于指明当前classLoader的父类加载器.
url: 类命名空间, 用于指明当前classLoader从哪里加载class文件.
2.Classloader的分类
2.1 BootstrapClassloader 启动类加载器
其实类加载器也是java类,也需要被加载。BootstrapClassloader就是最初加载Classloader的加载器。而BootstrapClassloader本身,是JVM启动时自动加载,是被C++启动的。
BootstrapClassloader 负责加载JDK中的核心类库,主要是JAVA_HOME/lib下的类,如rt.jar、resources.jar、charsets.jar等,这里包括其他的java自带的类加载器。
2.2 ExtClassloader 扩展类加载器
主要负责加载Java的扩展类库,默认加载JAVA_HOME/jre/lib/ext/目下的所有jar包。
2.3 AppClassloader 应用类加载器
主要负责加载classpath下的类,这里包括用户自己定义的类以及jar包。
2.4 自定义的类加载器
继承Classloader类实现自己的类加载器,默认父加载器AppClassloader。
这里的父加载器,并不是继承关系
3.Classloader的双亲委派
3.1什么是双亲委派
当一个类需要被加载时,当前的类加载器会把加载任务委派给parent加载器,parent加载器又会把加载任务委派给自己parent加载器,以此类推,
直到最顶层bootStrapClassLoader为止, 如果bootStrapClassLoader在自己的类空间(可以理解成上面提到的url)找到了该类的class文件, 就会去加载该类到内存中. 如果找不到, 会把任务再向下传递回extClassLoader, 让它去尝试加载该类. 依次类推, 直到某个类加载器在自己的类命名空间里找到了该类的class文件, 就会把该类加载进内存。
3.2双亲委派机制的用处
双亲委派机制其实是java的一种安全机制,能保证类在内存中只存在一份,所有程序使用的都是这一份, 不会出现自己定义一个String类, 被自己定义的classLoader给加载进来的情况, 因为rt.jar中的String会被最优先加载.
4.自定义Classloader
4.1实现方式
继承Classloader类,然后重写findClass方法,方法内去对应的路径下查找class文件,最后io读入,用defineClass把byte[] 转化为class对象。
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class DqClassLoader extends ClassLoader {
private String rootUrl;
public DqClassLoader(String rootPath) {
this.rootUrl = rootPath;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class clazz = null;
//根据类的二进制名称,获得该class文件的字节码数组
byte[] classData = getClassData(name);
if (classData == null) {
throw new ClassNotFoundException();
}
//将class的字节码数组转换成Class类的实例
clazz = defineClass(name, classData, 0, classData.length);
return clazz;
}
private byte[] getClassData(String name) {
InputStream is = null;
try {
String path = classNameToPath(name);
is = new FileInputStream(path);
byte[] buff = new byte[1024 * 4];
int len = -1;
ByteArrayOutputStream baos = new ByteArrayOutputStream();
while ((len = is.read(buff)) != -1) {
baos.write(buff, 0, len);
}
return baos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
private String classNameToPath(String name) {
return rootUrl + "/" + name.replace(".", "/") + ".class";
}
}
4.2 自定义加载器的应用场景
最常见的应用就是 Apache Tomcat 中的WebClassLoader,每个 Web 应用都有一个对应的类加载器实例。所不同的是它是首先尝试去加载某个类,如果找不到再代理给父类加载器。这与一般类加载器的顺序是相反的。其目的是使得 Web 应用自己的类的优先级高于 Web 容器提供的类。
当然,Java 核心库的类是不在查找范围之内的。这也是为了保证 Java 核心库的类型安全。
5.结尾
类加载器是 Java 语言的一个创新。它使得动态安装和更新软件组件成为可能。
在开发自己的类加载器的时候,需要注意与已有的类加载器组织结构的协调。