热加载
什么是热加载
热加载则是在运行时,重新编译后的单个字节码文件,不需要停止JVM就可以加载使用新的class文件。
实现热加载
1. 创建一个类用于被加载
public class HelloWorld {
public void sayHello() {
System.out.println("Hello, World07!");
}
}
2. 创建一个自定义类加载器
public class HotSwapClassLoader extends ClassLoader {
private static final String CLASS_PATH = "./target/classes/";
private final String className;
private byte[] classData;
public HotSwapClassLoader(String className) {
this.className = className;
}
@Override
public Class<?> findClass(String name) throws ClassNotFoundException {
if (!name.equals(className)) {
throw new ClassNotFoundException("Class " + name + " not found.");
}
if (classData == null) {
classData = loadClassData();
}
return defineClass(name, classData, 0, classData.length);
}
private byte[] loadClassData() {
try {
var classFilePath = Paths.get(CLASS_PATH, className.replace('.', '/') + ".class");
if (!Files.exists(classFilePath)) {
throw new RuntimeException("Class file not found: " + classFilePath);
}
byte[] classBytes = Files.readAllBytes(classFilePath);
// You can add a checksum here to ensure the class file has changed
// This is important for hot swapping to know when to reload the class
String checksum = getChecksum(classBytes);
System.out.println("Loaded class " + className + " with checksum: " + checksum);
return classBytes;
} catch (IOException e) {
throw new RuntimeException("Error loading class data", e);
}
}
private String getChecksum(byte[] bytes) {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(bytes);
byte[] digest = md.digest();
StringBuilder sb = new StringBuilder();
for (byte b : digest) {
sb.append(String.format("%02x", b & 0xff));
}
return sb.toString();
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Error calculating checksum", e);
}
}
/**
* 破坏双亲委派机制,自定义类加载方式
*/
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
Class<?> loadedClass = findLoadedClass(name);
if (null == loadedClass) {
try {
return findClass(name);
} catch (ClassNotFoundException e) {
return super.loadClass(name);
}
}
return loadedClass;
}
}
3. 主程序使用自定义类加载器加载该类;
首先使用自定义的类加载器HotSwapClassLoader加载HelloWorld类并调用其sayHello方法;然后,程序会等待你按回车键来模拟类文件的更新;此时修改HelloWorld的sayHello方法,build文件;按下回车键后,程序会创建一个新的HotSwapClassLoader实例来重新加载更新后的HelloWorld类,并再次调用sayHello方法。
public static void main(String[] args) throws Exception {
// 首次加载HelloWorld类
HotSwapClassLoader classLoader1 = new HotSwapClassLoader("com.example.demo.HelloWorld");
Class<?> helloWorldClass = classLoader1.loadClass("com.example.demo.HelloWorld");
// Class<?> helloWorldClass = classLoader1.findClass("com.example.demo.HelloWorld");
Object helloWorld = helloWorldClass.getDeclaredConstructor().newInstance();
helloWorldClass.getMethod("sayHello").invoke(helloWorld);
// 假设我们修改了HelloWorld类并重新编译了它,现在我们要热加载新的类
// 在实际场景中,你可能需要检测类文件的变化(例如通过文件监听器或检查时间戳)
// 这里我们简单地重新创建类加载器来模拟这个过程
// 等待用户输入,模拟类文件的更新
System.out.println("Press Enter to reload the class...");
System.in.read();
// 重新加载HelloWorld类
HotSwapClassLoader classLoader2 = new HotSwapClassLoader("com.example.demo.HelloWorld");
// Class<?> newHelloWorldClass = classLoader2.findClass("com.example.demo.HelloWorld");
Class<?> newHelloWorldClass = classLoader2.loadClass("com.example.demo.HelloWorld");
Object newHelloWorld = newHelloWorldClass.getDeclaredConstructor().newInstance();
newHelloWorldClass.getMethod("sayHello").invoke(newHelloWorld);
// 输出类加载器信息,确认是两个不同的类加载器加载的类
System.out.println("Class loaders are the same? " + (classLoader1 == classLoader2));
}
自定义类加载器
自定义类加载器的应用
- 热加载/热部署
- 加解密程序
...