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

前言

上期我们说了,自定义类加载器是如何打破双亲委派机制,同时又是如何实现神奇的热部署的效果的。这期我们带来一个简单的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类加载机制的工作原理及其灵活性。希望这次的学习能够帮助你在未来的开发工作中更加得心应手。

相关推荐
颜淡慕潇15 分钟前
【K8S系列】kubectl describe pod显示ImagePullBackOff,如何进一步排查?
后端·云原生·容器·kubernetes
Clarify1 小时前
docker部署go游戏服务器(进阶版)
后端
IT书架1 小时前
golang面试题
开发语言·后端·golang
机器之心2 小时前
全球十亿级轨迹点驱动,首个轨迹基础大模型来了
人工智能·后端
潜洋3 小时前
Spring Boot教程之五:在 IntelliJ IDEA 中运行第一个 Spring Boot 应用程序
java·spring boot·后端
St_Ludwig4 小时前
C语言 蓝桥杯某例题解决方案(查找完数)
c语言·c++·后端·算法·游戏·蓝桥杯
vener_4 小时前
LuckySheet协同编辑后端示例(Django+Channel,Websocket通信)
javascript·后端·python·websocket·django·luckysheet
计算机毕设孵化场5 小时前
计算机毕设-基于springboot的多彩吉安红色旅游网站的设计与实现(附源码+lw+ppt+开题报告)
vue.js·spring boot·后端·计算机外设·课程设计·计算机毕设论文·多彩吉安红色旅游网站
爪哇学长5 小时前
解锁API的无限潜力:RESTful、SOAP、GraphQL和Webhooks的应用前景
java·开发语言·后端·restful·graphql
战神刘玉栋5 小时前
《SpringBoot、Vue 组装exe与套壳保姆级教学》
vue.js·spring boot·后端