JAVA agentmain 热更新

使用JAVA虚拟机提供的agentmain实现热更新,主要涉及到的模块:java工具包tools.jar

  • 需要定义更新引擎
  • 定义更新执行器
  • 待更新的服务程序

1、快速开始

项目结构大致如下:
在这里插入图片描述

使用maven构建一个更新引擎,需要使用到maven编译jar插件,主要maven配置如下:

<plugin>
      <artifactId>maven-jar-plugin</artifactId>
      <version>3.0.2</version>
      <configuration>
           <archive>
              <manifestFile>META-INF/MANIFEST.MF</manifestFile>
           </archive>
      </configuration>
</plugin>

上述文件中只要定义了maven在编译打包jar文件时,指定一个配置文件,这个配置文件是agentmain执行的配置,该配置信息如下:

Manifest-Version: 1.0
Can-Redefine-Classes: true
Agent-Class: xin.spring.hotload.ServerAgent
Can-Retransform-Classes: true

上述文件主要配置说明:
Manifest-Version:指定版本信息
Can-Redefine-Classes:指定类是否可以重定义
Can-Retransform-Classes:指定类是否可以宠你想你转换定义
Agent-Class:更新引擎程序类

1、编写引擎类:ServerAgent

package xin.spring.hotload;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.instrument.ClassDefinition;
import java.lang.instrument.Instrumentation;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
 * 服务器代理
 *
 * @author spring
 * @date 2023/03/09
 */
public class ServerAgent {
    
    // 写一个日志记录格式方法
    public static void print(String str) {
        long time = System.currentTimeMillis();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String date = sdf.format(time);
        System.out.println(date + "----" + str);
    }
    
    // 读取class文件字节码
    public static byte[] fileToBytes(File file) throws IOException {
        FileInputStream in = new FileInputStream(file);
        byte[] bytes = new byte[in.available()];
        in.read(bytes);
        in.close();
        return bytes;
    }
    
    // 执行热更新
    public static void agentmain(String args, Instrumentation inst) throws Exception {
        long startTime = System.currentTimeMillis();
        print(Thread.currentThread().getName() + ":agent 启动成功,开始重定义对象....");
        print("args:" + args);
        String[] classPathArr = args.split(",");
        Class[] allClass = inst.getAllLoadedClasses();
//        print("allClass:" + Arrays.toString(allClass));
        Map<String, String> classMap = new HashMap<String, String>(16);

        String className;
        String filePath;
        for (int i = 0; i < classPathArr.length; i++) {
            String classPath = classPathArr[i];
            print("classpath:" + classPath);
            String[] arr = classPath.split("/");
            className = arr[arr.length - 1];
            filePath = classPath.replaceAll("\\.", "/") + ".class";
            classMap.put(className, filePath);
            print("targetPath:" + filePath);
        }
        print(classMap.keySet().toString());
        try {
            boolean isSuccess = false;

            for (int i = 0; i < allClass.length; i++) {
                Class c = allClass[i];
                className = c.getName();
                print("className:" + className);
                filePath = classMap.get(className);
                if (filePath != null) {
                    print("正在热更新class:" + className);
                    File file = new File(filePath);
                    try {
                        byte[] bytes = fileToBytes(file);
                        print("文件大小:" + bytes.length);
                        ClassDefinition classDefinition = new ClassDefinition(c, bytes);
                        inst.redefineClasses(new ClassDefinition[]{classDefinition});
                        isSuccess = true;
                    } catch (IOException var18) {
                        isSuccess = false;
                        var18.printStackTrace();
                        break;
                    }
                }
            }
            long endTime = System.currentTimeMillis();
            if (isSuccess) {
                print(args + "热更新成功,runtime(" + (endTime - startTime) + ")....finish");
            } else {
                print(args + "热更新失败,runtime(" + (endTime - startTime) + ")....failed");
            }
        } catch (Exception var19) {
            var19.printStackTrace();
        }
    }
}

2、定义一个主更新程序

该程序用于执行引擎服务

package xin.spring.hotload;

import com.sun.tools.attach.AgentInitializationException;
import com.sun.tools.attach.AgentLoadException;
import com.sun.tools.attach.AttachNotSupportedException;
import com.sun.tools.attach.VirtualMachine;

import java.io.IOException;

/**
 * 热更新服务器
 *
 * @author spring
 * @date 2023/03/09
 */
public class HotUpdateServer {

    public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException {
        if (args != null && args.length >= 3) {
            String agentJar = args[0];
            String[] pidArr = args[1].split(",");
            for (int i = 0; i < pidArr.length; i++) {
                String pid = pidArr[i];
                VirtualMachine vm = VirtualMachine.attach(pid);
                System.out.println("正在热更新的pid是:" + pid);
                vm.loadAgent(agentJar, args[2]);
            }
        } else {
            System.out.println("至少需要AgentJar包路径和一个进程id!!!");
        }
    }
}

3、编写一个简单的程序用于热更新

使用maven构建以个game-module,项目结构大致如下:
在这里插入图片描述

热更新前的代码

package xin.spring.game;
/**
 * 游戏逻辑
 *
 * @author spring
 * @date 2023/03/09
 */
public class GameLogic {

    private String name;

    public GameLogic(String name) {
        this.name = name;
    }

    public void play() {
        System.out.println(name + " is playing!!");
    }

}


热更新修改的代码

package xin.spring.game;

/**
 * 游戏逻辑
 *
 * @author spring
 * @date 2023/03/09
 */
public class GameLogic {

    private String name;

    public GameLogic(String name) {
        this.name = name;
    }

    public void play() {
        Date date = new Date();
        SimpleDateFormat format = new SimpleDateFormat("yyyy-mm-dd HH:MM:SS");                      
        System.out.println("["+format.format(date)+"]" + name + " is playing!!");
    }

}

执行游戏主程序:

import java.util.concurrent.TimeUnit;

/**
 * @author spring
 */
public class GameServerMain {

    public static void main(String[] args) throws InterruptedException {
        String name = "spring";
        if (args.length > 0) {
            name = args[0];
        }
        GameLogic gameLogic = new GameLogic(name);
        while (true) {
            gameLogic.play();
            TimeUnit.SECONDS.sleep(1);
        }
    }
}

将我们的游戏打包成jar文件

4、执行程序,实现热更新

1、启动game-module游戏

java -Dfile.encoding=UTF-8 -classpath C:\dev-tools\java\jdk1.8.0_311\jre\lib\charsets.jar;C:\dev-tools\java\jdk1.8.0_311\jre\lib\deploy.jar;C:\dev-tools\java\jdk1.8.0_311\jre\lib\ext\access-bridge-64.jar;C:\dev-tools\java\jdk1.8.0_311\jre\lib\ext\cldrdata.jar;C:\dev-tools\java\jdk1.8.0_311\jre\lib\ext\dnsns.jar;C:\dev-tools\java\jdk1.8.0_311\jre\lib\ext\jaccess.jar;C:\dev-tools\java\jdk1.8.0_311\jre\lib\ext\jfxrt.jar;C:\dev-tools\java\jdk1.8.0_311\jre\lib\ext\localedata.jar;C:\dev-tools\java\jdk1.8.0_311\jre\lib\ext\nashorn.jar;C:\dev-tools\java\jdk1.8.0_311\jre\lib\ext\sunec.jar;C:\dev-tools\java\jdk1.8.0_311\jre\lib\ext\sunjce_provider.jar;C:\dev-tools\java\jdk1.8.0_311\jre\lib\ext\sunmscapi.jar;C:\dev-tools\java\jdk1.8.0_311\jre\lib\ext\sunpkcs11.jar;C:\dev-tools\java\jdk1.8.0_311\jre\lib\ext\zipfs.jar;C:\dev-tools\java\jdk1.8.0_311\jre\lib\javaws.jar;C:\dev-tools\java\jdk1.8.0_311\jre\lib\jce.jar;C:\dev-tools\java\jdk1.8.0_311\jre\lib\jfr.jar;C:\dev-tools\java\jdk1.8.0_311\jre\lib\jfxswt.jar;C:\dev-tools\java\jdk1.8.0_311\jre\lib\jsse.jar;C:\dev-tools\java\jdk1.8.0_311\jre\lib\management-agent.jar;C:\dev-tools\java\jdk1.8.0_311\jre\lib\plugin.jar;C:\dev-tools\java\jdk1.8.0_311\jre\lib\resources.jar;C:\dev-tools\java\jdk1.8.0_311\jre\lib\rt.jar;D:\ubuntu-os\hotJava\game-module-1.0-SNAPSHOT.jar xin.spring.game.GameServerMain spring

2、将game-module按照更新的代码重新编译打包
3、将打包后的game-module解压到指定位置:D:/ubuntu-os/hotJava/xin.spring.game.GameLogic

jar -xvf D:\ubuntu-os\hotJava\game-module-1.0-SNAPSHOT.jar

4、使用jps查看当前执行game-module程序pid

5、执行GameServerMain 热更程序

java -Dfile.encoding=UTF-8 -classpath C:\dev-tools\java\jdk1.8.0_311\jre\lib\charsets.jar;C:\dev-tools\java\jdk1.8.0_311\jre\lib\deploy.jar;C:\dev-tools\java\jdk1.8.0_311\jre\lib\ext\access-bridge-64.jar;C:\dev-tools\java\jdk1.8.0_311\jre\lib\ext\cldrdata.jar;C:\dev-tools\java\jdk1.8.0_311\jre\lib\ext\dnsns.jar;C:\dev-tools\java\jdk1.8.0_311\jre\lib\ext\jaccess.jar;C:\dev-tools\java\jdk1.8.0_311\jre\lib\ext\jfxrt.jar;C:\dev-tools\java\jdk1.8.0_311\jre\lib\ext\localedata.jar;C:\dev-tools\java\jdk1.8.0_311\jre\lib\ext\nashorn.jar;C:\dev-tools\java\jdk1.8.0_311\jre\lib\ext\sunec.jar;C:\dev-tools\java\jdk1.8.0_311\jre\lib\ext\sunjce_provider.jar;C:\dev-tools\java\jdk1.8.0_311\jre\lib\ext\sunmscapi.jar;C:\dev-tools\java\jdk1.8.0_311\jre\lib\ext\sunpkcs11.jar;C:\dev-tools\java\jdk1.8.0_311\jre\lib\ext\zipfs.jar;C:\dev-tools\java\jdk1.8.0_311\jre\lib\javaws.jar;C:\dev-tools\java\jdk1.8.0_311\jre\lib\jce.jar;C:\dev-tools\java\jdk1.8.0_311\jre\lib\jfr.jar;C:\dev-tools\java\jdk1.8.0_311\jre\lib\jfxswt.jar;C:\dev-tools\java\jdk1.8.0_311\jre\lib\jsse.jar;C:\dev-tools\java\jdk1.8.0_311\jre\lib\management-agent.jar;C:\dev-tools\java\jdk1.8.0_311\jre\lib\plugin.jar;C:\dev-tools\java\jdk1.8.0_311\jre\lib\resources.jar;C:\dev-tools\java\jdk1.8.0_311\jre\lib\rt.jar; xin.spring.hotload.HotUpdateServer
{更新引擎D:\ubuntu-os\hotJava\hot-load-agent-1.0-SNAPSHOT.jar} {JAVAPID} {更新的类D:/ubuntu-os/hotJava/xin.spring.game.GameLogic}

注意:xin.spring.hotload.HotUpdateServer后面的三个参数分别是:执行引擎jar、要更新的java程序pid、待更新的类项目目录+类全名

执行结果:
更新之前:在这里插入图片描述

更新中:
在这里插入图片描述
更新后的执行结果:

在这里插入图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java Agent是一种Java技术,它可以在程序运行时动态地修改或增强现有的Java类。Java Agent可以用于很多场景,其中之一就是实现Java应用程序的更新。下面是一个简单的Java Agent更新的示例: 1.编写Java Agent程序,实现更新功能。具体实现可以使用Java工具包tools.jar提供的agentmain方法,该方法可以在程序运行时动态地修改或增强现有的Java类。以下是一个简单的Java Agent程序示例: ```java public class HotUpdateAgent { public static void agentmain(String agentArgs, Instrumentation inst) throws Exception { // 获取要更新的类的类名 String className = "com.example.demo.HelloWorld"; // 获取要更新的类的字节码文件 byte[] classBytes = getClassBytes(className); // 重新定义要更新的类 ClassDefinition classDefinition = new ClassDefinition(Class.forName(className), classBytes); inst.redefineClasses(classDefinition); } private static byte[] getClassBytes(String className) throws Exception { // 读取要更新的类的字节码文件 FileInputStream fis = new FileInputStream(className.replaceAll("\\.", "/") + ".class"); ByteArrayOutputStream bos = new ByteArrayOutputStream(); byte[] buffer = new byte[1024]; int len; while ((len = fis.read(buffer)) != -1) { bos.write(buffer, 0, len); } fis.close(); bos.close(); return bos.toByteArray(); } } ``` 2.在需要更新Java应用程序中,使用Java Agent实现更新。具体实现可以使用Java命令的-javaagent参数来指定Java Agent程序,并使用HotUpdateServer类来启动Java应用程序。以下是一个简单的Java应用程序示例: ```java public class HelloWorld { public static void main(String[] args) throws Exception { while (true) { System.out.println("Hello World!"); Thread.sleep(1000); } } } ``` 3.使用HotUpdateServer类来启动Java应用程序,并使用Java命令的-javaagent参数来指定Java Agent程序。以下是一个简单的HotUpdateServer类示例: ```java public class HotUpdateServer { public static void main(String[] args) throws Exception { // 获取要更新Java应用程序的pid String pid = args[0]; // 获取要更新的类的项目目录和类名 String classPath = args[1]; // 获取执行引擎jar的路径 String enginePath = args[2]; // 启动Java应用程序 Process process = Runtime.getRuntime().exec("java -javaagent:" + enginePath + " -cp " + classPath + " HelloWorld"); // 获取Java应用程序的输入流和输出流 InputStream inputStream = process.getInputStream(); OutputStream outputStream = process.getOutputStream(); // 启动更新线程 new Thread(() -> { while (true) { try { // 等待用户输入命令 Scanner scanner = new Scanner(System.in); String command = scanner.nextLine(); // 发送命令到Java应用程序 outputStream.write((command + "\n").getBytes()); outputStream.flush(); // 读取Java应用程序的输出 byte[] buffer = new byte[1024]; int len; while ((len = inputStream.read(buffer)) != -1) { System.out.write(buffer, 0, len); } } catch (Exception e) { e.printStackTrace(); } } }).start(); // 等待Java应用程序退出 process.waitFor(); } } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值