概述
- 什么是类加载器
类加载器用来加载类的类:ClassLoader。把.class文件加载到JVM的方法区中,变成一个Class对象 - 得到类加载器
Class的方法:getClassLoader() - 类加载器的分类
(1)引导:负责加载类库
(2)扩展:负责加载扩展jar包
(3)系统:负责加载应用下的class,包含开发人员写的类,和第三方的jar包。(即classpath下的类)
分级:引导是扩展的上级,扩展是系统的上级
JVM眼中的类
在jvm中一个类不能被加载两次。如果一个类被加载了,再次加载这个类时,加载器会先去查找这个类是否已经被加载,如果加载就不再加载。
如果一个类使用不同的加载器加载,就可以出现多次加载的情况。在jvm眼中,相同的类需要有相同的.class文件和相同的类加载器。
类的委托机制
在代码中出现 new A();
系统发现自己加载的类中包含:new A();,说明需要去加载A类
通过上面的图解,可以得到,优先级是 引导>扩展>系统。这样的好处就是保证了JDK中的类一定由引导类加载器加载。
注意:如果某一级类加载器加载了A类,那么,该类中出现的其他需要被加载的类,也有该级类加载器加载。
自定义类加载器
我们也可以通过继承ClassLoader类来完成自定义类加载器,自类加载器的目的一般是为了加载网络上的类,因为这会让class在网络中传输,为了安全,那么class一定是需要加密的,所以需要自定义的类加载器来加载(自定义的类加载器需要做解密工作)。
ClassLoader加载类都是通过loadClass()方法来完成的,loadClass()方法的工作流程如下:
- 调用findLoadedClass()方法查看该类是否已经被加载过了,如果该没有加载过,那么这个方法返回null;
- 判断findLoadedClass()方法返回的是否为null,如果不是null那么直接返回,这可以避免同一个类被加载两次;
- 如果findLoadedClass()返回的是null,那么就启动代理模式(委托机制),即调用上级的loadClass()方法,获取上级的方法是getParent(),当然上级可能还有上级,这个动作就一直向上走;
- 如果getParent().loadClass()返回的不是null,这说明上级加载成功了,那么就加载结果;
- 如果上级返回的是null,这说明需要自己出手了,这时loadClass()方法会调用本类的findClass()方法来加载类;
- 这说明我们只需要重写ClassLoader的findClass()方法,这就可以了!如果重写了loadClass()方法覆盖了代理模式!
自定义一个类加载器,只需要继承ClassLoader类,然后重写**findClass()**方法。
- 找到class文件,把它加载到一个byte[]中
- 调用ClassLoader类的defineClass(),传递一个byty[]进入,得到该class文件生成的Class对象
FileSystemClassLoader
public class FileSystemClassLoader extends ClassLoader{
private String classpath;//设置自定义类加载器负责的范围:只在这里去查找类
public FileSystemClassLoader() {}
public FileSystemClassLoader(String classpath) {
this.classpath = classpath;
}
//通过名称找到.class文件
@Override
public Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] datas = getClassData(name);
if(datas == null) {
throw new ClassNotFoundException("类没有找到:" + name);
}
return this.defineClass(name, datas, 0, datas.length);
} catch (IOException e) {
e.printStackTrace();
throw new ClassNotFoundException("类找不到:" + name);
}
}
//根据类名,查找到该.class文件,并加载到一个字节数组中
private byte[] getClassData(String name) throws IOException {
//将类名中的.转化成\,即各级文件夹名称
name = name.replace(".", "\\") + ".class";
//添加上查找范围
File classFile = new File(classpath, name);
//调用commons-io.jar工具方法,加载到字节数组中,所以需要导commons-io的jar包
return FileUtils.readFileToByteArray(classFile);
}
}
这里为了测试,自己写一个工具类,生成jar包,然后解压,放到如下位置
测试类
@Test
public void func() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
//得到类加载器,并指定查找范围
ClassLoader loader=new FileSystemClassLoader("G:\\ClassPath");
//指定查找类名
Class clazz= loader.loadClass("cn.edu.stu.tools.commons.CommonUtils");
//通过反射,得到方法
Method method=clazz.getMethod("uuid");
String uuid= (String) method.invoke(clazz);
System.out.println(uuid);
}
结果:
79E71E8F90D64DEF80964AAA4BB326E8
调用的方法是生成一个uuid
Tomcat类加载器
tomcat提供了两种类加载器
- 服务器类加载器:加载${CATALINA_HOME}\lib
- 应用类加载器:${CONTEXT_HOME}\WEB-INF\lib、${CONTEXT_HOME}\WEB-INF\classes,应用类加载器,它负责加载这两个路径下的类
但Tomcat提供的类加载器不会使用传统的代理模式,而是自己先去加载,如果加载不到,再使用代理模式。