我们除了在面试中遇到类的加载器的概率高一点外,在平时的工作中其实是比较少接触的,你平时工作中很少有说需要你从外部环境去获取加载class字节码文件的,但是在学习自定义类加载器的过程中,我们会对JVM的类加载机制和类加载过程更加了解,所以我们还是有必要来学习一下的。
在java中,类加载器(ClassLoader)是负责将类文件加载到JVM中的组件。实现自定义类加载器可以让你控制类加载的过程,比如可以从网络、数据库、从你指定的磁盘位置等地方加载类。
一、自定义类加载器的步骤
-
继承ClassLoader类:自定义的类加载器需要继承java.lang.ClassLoader类。
-
重写findClass方法:重写findClass(String name)方法,这个方法用于定义类的加载逻辑。
-
调用defineClass方法:在findClass方法中,通过defineClass方法将字节数组转换为Class对象。
从文件系统加载类,代码示例如下:
先重写findClass方法:
public class MyClassLoader extends ClassLoader{
private String classPath;
public MyClassLoader(String classPath){
this.classPath = classPath;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
// 将类名转换成文件路径(我这个例子中是 D:/pojo/Test.class)
String fileName = classPath + name.replaceAll("\\.", "/") + ".class";
//读取类文件中的字节码
byte[] classBytes = Files.readAllBytes(Paths.get(fileName));
return defineClass(name, classBytes, 0, classBytes.length);
}catch (Exception e){
throw new ClassNotFoundException(name);
}
}
}
然后我准备了一个Test类
然后我们要将这个Test类用javac命令进行编译:
我们将这个Test.class字节码文件拷贝到D盘个目录里面去:
测试类加载器:
public class TestDemo {
public static void main(String[] args) {
try {
//创建自定义类加载器,指定类文件所在路径(我们在上一步已经将Test.class字节码文件拷贝到D盘根目录中去)
MyClassLoader myClassLoader = new MyClassLoader("D:/");
//加载类
Class<?> clazz = myClassLoader.loadClass("pojo.Test");//参数为类的全限定类名
//得到class字节码后,使用反射来创建对象
Object instance = clazz.getDeclaredConstructor().newInstance();
//打印出这个对象所属的类名
System.out.println(instance.getClass().getName());
//打印出instance这个对象所属的类 用的类加载器的名称
System.out.println("该类由:"+instance.getClass().getClassLoader().getClass().getName()+" 进行加载");
//调用instance实例中的方法
clazz.getMethod("eat").invoke(instance);
} catch (Exception e) {
e.printStackTrace();
}
}
}
结果:
由结果可以看到,自己写的类加载器,已经把我电脑D盘下的 Test.class字节码文件,加载进JVM了,并且使用自定义类加载器加载进来的字节码反射生成了一个instance对象。
总结
自定义类加载器的核心在于对字节码文件的获取。这里我们需要注意几个点:
- 最好不要重写loadClass方法,因为这样容易破坏双亲委托模式。
- 这里Test 类本身可以被AppClassLoader类加载,因此我们不能把Test.class放在类路径下。否则,由于双亲委托机制的存在,会直接导致该类由AppClassLoader加载,而不会通过我们自定义类加载器来加载。