GitHub - pandening/Java-debug-tool: Java dynamic debug tool
instrumentation接口,DefineClass数据结构包装类,包装类外部类,重新加载类。
热修复思路:通过javaagent插件打桩,premain方法,启动一个线程,该线程定时执行一个任务,该任务从外部文件系统加载编译好的class文件,调用instrumentation接口的redefineClasses,在启动时加载class。
redefineClasses(ClassDefinition... var1)
javaagent插件打桩,即在jvm 启动参数中,加入;-javaagent: agentdemo.jar
类似jacoco,skywalking都是采用这种方式。
1,配置启动参数,-javaagent:/Users/lijunfeng/gits/hotagent/hot-agent/target/hot-agent-1.0-SNAPSHOT.jar
源代码如下:
package org.example.agent;
import org.example.service.HotFixProcess;
import java.lang.instrument.Instrumentation;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class HotCodeAgent {
public static void premain(String agentArgs, Instrumentation inst) {
Executors.newScheduledThreadPool(1).scheduleAtFixedRate(
new HotFixProcess(inst), 1, 1, TimeUnit.SECONDS
);
}
}
package org.example.service;
import org.example.bean.FileBean;
import java.io.File;
import java.io.FileInputStream;
import java.lang.instrument.ClassDefinition;
import java.lang.instrument.Instrumentation;
import java.util.ArrayList;
import java.util.List;
public class HotFixProcess implements Runnable {
String path = "/Users/lijunfeng/gits/hotagent/file";
private Instrumentation instm;
public HotFixProcess(Instrumentation inst) {
this.instm = inst;
}
@Override
public void run() {
try {
List<FileBean> classNames = getClasses();
for(FileBean fileBean:classNames) {
Class clz = Class.forName(fileBean.getClassName());
byte[] bytes = getBytes(fileBean.getFileName());
reload(clz, bytes);
fileBean.getFile().delete();
System.out.println("start replace file" + fileBean.getFileName() + ", class:" + fileBean.getClassName());
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 热更新
* 重新定义可能会改变方法体,常量池和属性。重定义不能添加,删除或重命名字段或方法,更改方法的签名或更改继承。这些限制可能在将来的版本中解除。
* 类文件字节不会被检查,验证和安装,直到应用转换为止,如果结果字节错误,则此方法将抛出异常。 如果此方法抛出异常,则不会重新定义任何类。
*/
public void reload(Class<?> clazz, byte[] data) {
try {
ClassDefinition classDefinition = new ClassDefinition(clazz, data);
instm.redefineClasses(classDefinition);
} catch (Throwable e) {
e.printStackTrace();
}
}
private byte[] getBytes(String fileName) throws Exception {
File file = new File(fileName);
FileInputStream in = new FileInputStream(file);
byte[] bytes = new byte[(int)file.length()];
in.read(bytes);
return bytes;
}
private List<FileBean> getClasses() {
List<FileBean> beans = new ArrayList<>();
File file = new File(path);
addClassName(beans, file);
return beans;
}
private void addClassName(List<FileBean> beans, File file) {
File[] files = file.listFiles();
for (File f : files) {
if(f.isFile() && f.getName().contains("class")) {
FileBean bean = new FileBean();
bean.setFile(f);
bean.setClassName(f.getName().substring(0, f.getName().lastIndexOf(".")));
bean.setFileName(f.getAbsolutePath());
beans.add(bean);
}else if(f.isDirectory()) {
addClassName(beans, f);
}
}
}
}
package org.example.bean;
import java.io.File;
public class FileBean {
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
public File getFile() {
return file;
}
public void setFile(File file) {
this.file = file;
}
private String fileName;
private String className;
private File file;
}