通过实现热加载学习自定义类加载器通过实现自定义类加载器学习类的加载

88 阅读2分钟

热加载

什么是热加载

热加载则是在运行时,重新编译后的单个字节码文件,不需要停止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方法;然后,程序会等待你按回车键来模拟类文件的更新;此时修改HelloWorldsayHello方法,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));
    }  

自定义类加载器

自定义类加载器的应用

  1. 热加载/热部署
  2. 加解密程序

...