沃趣,常用的热部署原理竟然是这样的

前言

上期我们说了,自定义类加载器是如何打破双亲委派机制,同时又是如何实现神奇的热部署的效果的。这期我们带来一个简单的demo,话不多说,开始了。

一、实现

1.1、自定义类加载器

在这里,我们通过继承ClassLoader类,覆盖了 ClassLoaderloadClass 方法来实现特定的类加载逻辑。

核心作用如下:负责加载 org.example.test 包下的类,并从项目的 target 目录下的相应 .class 文件加载这些类。如果请求加载的类不属于指定的包,则交由父类加载器处理。加载过程中,它使用 FileInputStream 读取类文件,并调用 defineClass 将字节码转换为 Class 对象。最后,确保在读取完成后关闭输入流。这个类加载器适用于需要动态加载特定包内类的场景。

java 复制代码
package org.example.test;
​
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Objects;
​
/**
 * @author MADAO
 */
public class CustomClassLoader extends ClassLoader{
​
    /**
     * 这里可以获取项目的target根目录
     */
    private final String loadPath = Objects.requireNonNull(CustomClassLoader.class.getResource("/").getPath());
​
    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        //这里要做判断,只有项目下的类可以使用该加载器加载
        if(!name.startsWith("org.example.test")) {
            return super.loadClass(name);
        }
        //com.xx.xxx -> com/xx/xxx
        final String s = name.replaceAll("\.", "/");
        InputStream stream = null;
        try {
            System.out.println("loadPath: " + loadPath + s + ".class");
            stream = new FileInputStream(loadPath + s + ".class");
            final int len = stream.available();
            final byte[] bytes = new byte[len];
            stream.read(bytes);
            //将byte[]转化为class对象
            return defineClass(name, bytes,0 , len);
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException("读取资源时出现异常");
        } finally {
            try {
                if(stream != null){
                    stream.close();
                }
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
​
    }
}
​

1.2、实现需要被加载的类

这里没啥好说的,就是实现一个简单的类,让其被加载器加载

csharp 复制代码
package org.example.test;
​
/**
 * @author MADAO
 */
public class HotDeploy {
    public void test1 () {
        System.out.println("test");
    }
}
​

1.3、实现运行

下面的代码展示了如何使用自定义的类加载器 CustomClassLoader 来实现实时编译和热部署的功能。具体来说,这段代码的作用如下:

  1. 循环加载和执行 :程序进入一个无限循环,在每次循环中,它使用 CustomClassLoader 加载并执行 org.example.test.HotDeploy 类中的 test1 方法。
  2. 方法调用 :通过反射机制,找到 HotDeploy 类中的 test1 方法,并创建该类的一个实例,然后调用该方法。
  3. 休眠:每次方法调用之后,程序会休眠 5 秒钟。
  4. 编译源代码 :休眠结束后,程序尝试编译位于指定路径下的 HotDeploy.java 文件。编译命令使用的是 javac,并且指定了输出目录为 E:/code/TechTrial/TechTrial_backend/api/target/classes
  5. 检查编译结果:编译命令执行完毕后,程序会检查编译命令的退出码来验证编译是否成功。
java 复制代码
package org.example.test;
​
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
​
public class Test {
    public static void main(String[] args) {
             while (Boolean.TRUE) {
                 try {
                     //使用自定义类加载器进行加载类
                     final CustomClassLoader loader1 = new CustomClassLoader();
                     final Class<?> clazz = loader1.loadClass("org.example.test.HotDeploy");
                     final Method method = clazz.getMethod("test1");
                     final Object o = clazz.getConstructor().newInstance();
                     method.invoke(o);
​
                     // 休眠5秒
                     Thread.sleep(5000);
​
                     // 执行编译命令,将HotDeploy.java文件编译成字节码文件(.class文件)
                     Process process = Runtime.getRuntime().exec(
                             "cmd /c cd E:\code\TechTrial\TechTrial_backend\api\src\main\java\org\example\test && javac -d E:/code/TechTrial/TechTrial_backend/api/target/classes HotDeploy.java"
                     );
​
                     // 检查编译命令是否成功执行
                     int exitCode = process.waitFor();
                     if (exitCode != 0) {
                         System.err.println("编译命令执行失败,退出码:" + exitCode);
                     } else {
                         System.out.println("编译命令执行成功");
                     }
                 } catch (ClassNotFoundException | NoSuchMethodException | InstantiationException |
                          IllegalAccessException | InvocationTargetException | InterruptedException |
                          IOException e) {
                     e.printStackTrace();
             }
        }
    }
}
​

二、总结

通过本次的探索,我们了解了如何利用自定义类加载器来实现Java应用中的热部署功能。自定义类加载器打破了传统的类加载机制,允许我们在不重启应用的情况下更新代码逻辑。具体而言,我们定义了一个 CustomClassLoader,它专门负责加载 org.example.test 包下的类,并且通过读取本地的 .class 文件来动态加载这些类。

总之,自定义类加载器为我们提供了一种灵活的方式来处理动态加载和更新代码的需求,同时也是一次有趣的实践机会,让我们深入理解了Java类加载机制的工作原理及其灵活性。希望这次的学习能够帮助你在未来的开发工作中更加得心应手。

相关推荐
J老熊32 分钟前
Nginx 的讲解和案例示范
linux·运维·后端·nginx·面试
Loong_DQX1 小时前
【flask】 前后端通信方式 原生js的ajax,总结
后端·python·flask
王大锤43911 小时前
golang的多表联合orm
开发语言·后端·golang
好看资源平台2 小时前
快速上手 Rust——实用示例
开发语言·后端·rust
许野平2 小时前
Rust:文档注释 //! 和 ///
开发语言·后端·rust·注释·文档注释
陈序缘2 小时前
Rust 力扣 - 238. 除自身以外数组的乘积
开发语言·后端·算法·leetcode·rust
喝旺仔la2 小时前
Django后台接口开发
后端·python·django
程序员阿鹏2 小时前
详解:模板设计模式
java·开发语言·jvm·后端·设计模式·eclipse·1024程序员节
customer083 小时前
【开源免费】基于SpringBoot+Vue.J影城管理系统(JAVA毕业设计)
java·vue.js·spring boot·后端·spring cloud·开源
叫我阿笑就好了3 小时前
Json库和文件操作
后端·json·restful