如前所述,
- 每个类加载器都会记住(缓存)之前加载的类,并且不会再次重新加载 - 基本上每个类加载器都定义了一个命名空间。
- 子类加载器将类加载委托给父类加载器,即
Java 8 及之前的版本
自定义类加载器 -> 应用类加载器 -> 扩展类加载器 -> 引导类加载器
Java 9+
自定义类加载器 -> 应用类加载器 -> 平台类加载器 -> 引导类加载器。
从上面我们可以得出结论,每个 Class 对象都是由其完全限定的类名和定义它的加载器(也称为定义的加载器)标识的
来自Javadocs:
每个 Class 对象都包含对 ClassLoader 的引用
定义它。
defineClass 方法将字节数组转换为
类 类。可以创建这个新定义的类的实例
使用 Class.newInstance。
重新加载类的简单解决方案是定义新的(例如UrlClassLoader)或您自己的自定义类加载器。
对于需要替换类的更复杂的场景,可以使用动态代理机制。
请参阅下面的简单解决方案,我通过定义自定义类加载器来重新加载相同的类以解决类似问题。
本质——重写父类加载器的findClass方法,然后从文件系统读取的字节中加载类。
- MyClassLoader - 覆盖
findClass 并执行 defineClass
package com.example.classloader;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
public class MyClassLoader extends ClassLoader {
private String classFileLocation;
public MyClassLoader(String classFileLocation) {
this.classFileLocation = classFileLocation;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] classBytes = loadClassBytesFromDisk(classFileLocation);
return defineClass(name, classBytes, 0, classBytes.length);
}
private byte [] loadClassBytesFromDisk(String classFileLocation) {
try {
return Files.readAllBytes(Paths.get(classFileLocation));
}
catch (IOException e) {
throw new RuntimeException("Unable to read file from disk");
}
}
}
- SimpleClass - 实验主题 -
** 重要:使用
javac 编译,然后从类路径中删除 SimpleClass.java(或者只是重命名它)
否则,由于类加载委托机制,它将由系统类加载器加载。**
来自src/main/java
javac com/example/classloader/SimpleClass.java
package com.example.classloader;
public class SimpleClassRenamed implements SimpleInterface {
private static long count;
public SimpleClassRenamed() {
count++;
}
@Override
public long getCount() {
return count;
}
}
- SimpleInterface - 主题接口:将接口与实现分离以编译和执行主题的输出。
package com.example.classloader;
public interface SimpleInterface {
long getCount();
}
- 驱动程序 - 执行测试
package com.example.classloader;
import java.lang.reflect.InvocationTargetException;
public class MyClassLoaderTest {
private static final String path = "src/main/java/com/example/classloader/SimpleClass.class";
private static final String className = "com.example.classloader.SimpleClass";
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException,
InvocationTargetException, InstantiationException { // Exception hell due to reflection, sorry :)
MyClassLoader classLoaderOne = new MyClassLoader(path);
Class<?> classOne = classLoaderOne.loadClass(className);
// we need to instantiate object using reflection,
// otherwise if we use `new` the Class will be loaded by the System Class Loader
SimpleInterface objectOne =
(SimpleInterface) classOne.getDeclaredConstructor().newInstance();
// trying to re-load the same class using same class loader
classOne = classLoaderOne.loadClass(className);
SimpleInterface objectOneReloaded = (SimpleInterface) classOne.getDeclaredConstructor().newInstance();
// new class loader
MyClassLoader classLoaderTwo = new MyClassLoader(path);
Class<?> classTwo = classLoaderTwo.loadClass(className);
SimpleInterface ObjectTwo = (SimpleInterface) classTwo.getDeclaredConstructor().newInstance();
System.out.println(objectOne.getCount()); // Outputs 2 - as it is the same instance
System.out.println(objectOneReloaded.getCount()); // Outputs 2 - as it is the same instance
System.out.println(ObjectTwo.getCount()); // Outputs 1 - as it is a distinct new instance
}
}