docs: Translate the Chinese comments in the event module into English.

This commit is contained in:
jaysunxiao committed 2026-05-07 17:01:31 +08:00
1 parent 6a4e71b2f1
commit 000691db86
6 files changed
+68 -68

No files matched your search

@@ -8,9 +8,9 @@ import java.lang.instrument.ClassDefinition;
import java.lang.instrument.Instrumentation;
/**
* 使用 Instrumentation,开发者可以构建一个独立于应用程序的代理程序(Agent),用来监测和协助运行在 JVM 上的程序,
* 甚至能够替换和修改某些类的定义。有了这样的功能,开发者就可以实现更为灵活的运行时虚拟机监控和 Java 类操作了,
* 这样的特性实际上提供了一种虚拟机级别支持的 AOP 实现方式,使得开发者无需对 JDK 做任何升级和改动,就可以实现某些 AOP 的功能了。
* Using Instrumentation, developers can build an agent program independent of the application to monitor and assist programs running on the JVM,
* and even replace or modify class definitions. This enables flexible runtime JVM monitoring and Java class manipulation,
* effectively providing a JVM-level AOP mechanism without any JDK changes or upgrades.
*
* @author godotg
*/
@@ -24,11 +24,11 @@ public class HotSwapAgent {
/*
Java SE6开始,提供了在应用程序的VM启动后在动态添加代理的方式,即agentmain方式。 与Permain类似,agent方式同样需要提供一个agent jar,并且这个jar需要满足:
在manifest中指定Agent-Class属性,值为代理类全路径
代理类需要提供public static void agentmain(String args, Instrumentation inst)或public static void agentmain(String args)方法。并且再二者同时存在时以前者优先。args和inst和premain中的一致。
不过如此设计的再运行时进行代理有个问题——如何在应用程序启动之后再开启代理程序呢? JDK6中提供了Java Tools API,其中Attach API可以满足这个需求。
Attach API中的VirtualMachine代表一个运行中的VM。其提供了loadAgent()方法,可以在运行时动态加载一个代理jar。
Since Java SE 6, agents can be attached dynamically after the JVM starts via the agentmain method. Like premain, it requires an agent JAR that satisfies:
- Manifest must specify the Agent-Class attribute with the fully-qualified agent class name
- The agent class must provide agentmain(String args, Instrumentation inst) or agentmain(String args); the former takes priority if both exist. args and inst are the same as in premain.
However, this raises a question: how to attach an agent after the application has started? JDK6's Java Tools API provides the Attach API to solve this.
VirtualMachine in the Attach API represents a running JVM and provides loadAgent() to dynamically load an agent JAR at runtime.
*/
public static void agentmain(String args, Instrumentation inst) {
Class<?> clazz = null;
@@ -40,16 +40,16 @@ public class HotSwapAgent {
int len = dis.readInt();
ClassDefinition[] classDefs = new ClassDefinition[len];
for (int i = 0; i < len; i++) {
//类名
// Class name
String className = dis.readUTF();
//类字节码
// Class bytecode
byte[] classBytes = new byte[dis.readInt()];
dis.readFully(classBytes);
classDefs[i] = new ClassDefinition(Class.forName(className), classBytes);
}
//开始更新
// Start update
inst.redefineClasses(classDefs);
//更新成功
// Update successful
} catch (Exception e) {
if (clazz != null) {
try {
@@ -24,17 +24,17 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
//java11过后ClassFile对外不可见
// After Java 11, ClassFile is no longer publicly visible
//import com.sun.tools.classfile.ClassFile;
/**
* JMX(JAVA Management Extensions)技术是java5的新特性,它提供一种简单,标准的方式去管理应用程序,设备,服务等资源。
* JMS定义了一些设计模式,api和一些服务来进行应用程序和网络的监控,这些都是基于java语言环境的。
* 使用JMS技术,资源被一种叫做MBeans(Managed Beans)监控,这些MBean都在一个核心对象管理server上注册。
* JMS给java开发者提供了自由的方式去监控java代码,创建智能java agents,实现分布式管理的中间件和管理者,并且能够快速整合这些方案到的管理和监控系统。
* JMX (Java Management Extensions) is a Java 5 feature providing a simple, standard way to manage applications, devices, and services.
* JMX defines design patterns, APIs, and services for monitoring applications and networks within the Java environment.
* With JMX, resources are managed by MBeans (Managed Beans) registered with a central MBean server.
* JMX gives Java developers a flexible way to monitor Java code, create intelligent agents, build distributed management middleware, and integrate into management/monitoring systems.
* <p>
* 根据JMX描述,MBean接口包括一些可读或者可写的属性,还有一些定义好的方法,这些方法能够被MBean管理应用程序调用。
* 实现类,类名必须为接口sufixMBean的前缀。也就是Hello。如果不按这个命名注册MBean就会有问题。
* Per the JMX spec, an MBean interface exposes readable/writable attributes and defined methods callable by management applications.
* Implementation class name must be the interface name without the 'MBean' suffix (e.g., 'Hello' for 'HelloMBean'). Non-compliance causes registration errors.
*
* @author godotg
*/
@@ -44,7 +44,7 @@ public class HotSwapServiceMBean implements IHotSwapServiceMBean {
private static final Logger logger = LoggerFactory.getLogger(HotSwapServiceMBean.class);
// 热更新的代理和热更新的文件要放在同一个目录
// The hot-swap agent and hot-swap files must be in the same directory
private static final String HOT_SWAP_SCRIPT = "hotscript";
private static final String HOT_SWAP_AGENT = HOT_SWAP_SCRIPT + "/hotswap-2.0.jar";
@@ -57,15 +57,15 @@ public class HotSwapServiceMBean implements IHotSwapServiceMBean {
registerMBean();
}
// 使用jconsole去连接,当然也可以使用RMI进行远程连接MBean server,来进行管理和执行操作。
// Connect via jconsole or remotely via RMI to the MBean server for management
private void registerMBean() {
//注册监控
// Register monitoring
try {
//获取MBeanServer 如果没有MBean server存在那么下面会自动调用ManagementFactory.createMBeanServer()
// Get MBeanServer; if none exists, ManagementFactory.createMBeanServer() is called automatically
MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
// 包名加 类名 创建一个ObjectName
// Create ObjectName from package + class name
ObjectName objectName = new ObjectName(this.getClass().getPackage().getName() + ":type=" + this.getClass().getSimpleName());
// 在MBean server上注册MBean
// Register MBean with the MBean server
mbs.registerMBean(this, objectName);
} catch (Exception e) {
logger.error("MBean error", e);
@@ -83,11 +83,11 @@ public class HotSwapServiceMBean implements IHotSwapServiceMBean {
@Override
public synchronized void hotSwapByAbsolutePath(String absolutePath) {
// 热更新class文件,上一步没有完成的操作本次继续执行
// Hot-swap class files; continue any operations not completed in the previous pass
try {
hotSwapClass(absolutePath);
} catch (Exception e) {
logger.error("热更新class文件异常:[exception:{}]", e);
logger.error("Hot-swap class file error: [exception:{}]", e);
}
}
@@ -95,7 +95,7 @@ public class HotSwapServiceMBean implements IHotSwapServiceMBean {
public void logAllUpdateClassFileInfo() {
int count = 0;
for (Map.Entry<String, ClassFileDef> entry : HotSwapManager.getInstance().getClassFileDefMap().entrySet()) {
logger.info("[{}].更新的类名称:[{}],更改的时间:[{}]", ++count, entry.getKey(), entry.getValue().getLastModifyTime());
logger.info("[{}]. Updated class: [{}], modified time: [{}]", ++count, entry.getKey(), entry.getValue().getLastModifyTime());
}
}
@@ -106,7 +106,7 @@ public class HotSwapServiceMBean implements IHotSwapServiceMBean {
ByteArrayOutputStream baos = null;
DataOutputStream dos = null;
try {
// 本次需要更新的Class
// Classes to update in this pass
Map<String, ClassFileDef> updateClassMap = new HashMap<>();
List<File> fileList = FileUtils.getAllReadableFiles(new File(absolutePath));
for (File file : fileList) {
@@ -122,60 +122,60 @@ public class HotSwapServiceMBean implements IHotSwapServiceMBean {
}
if (updateClassMap.isEmpty()) {
logger.debug("本次更新没有如何文件");
logger.debug("No files to update in this pass");
return;
}
// 验证本次更新的所有class文件是否合法
// Validate all class files to be updated in this pass
Map<String, ClassFileDef> classFileDefMap = HotSwapContext.getHotSwapManager().getClassFileDefMap();
for (Map.Entry<String, ClassFileDef> entry : updateClassMap.entrySet()) {// 读取所有本次需要热更新的字节码
for (Map.Entry<String, ClassFileDef> entry : updateClassMap.entrySet()) {// Read all bytecode to be hot-swapped in this pass
ClassFileDef classFileDef = entry.getValue();
// 上一次热更新的文件
// Files from the previous hot-swap
ClassFileDef lastClassFileDef = classFileDefMap.get(classFileDef.getClassName());
if (lastClassFileDef != null && !classFileDef.getClassName().equals(lastClassFileDef.getClassName())) {
logger.error("本次热更新失败,两次热更新文件的类名称不一致,转换失败:[{}]-->[{}]"
logger.error("Hot-swap failed: class names inconsistent between passes [{}]-->[{}]"
, classFileDef.getClassName(), lastClassFileDef.getClassName());
return;
}
}
// 创建临时更新流
// Create temporary update stream
baos = new ByteArrayOutputStream(1024);
dos = new DataOutputStream(baos);
// 先写需要热更新类的数量
// Write the number of classes to hot-swap first
dos.writeInt(updateClassMap.size());
for (ClassFileDef classFileDef : updateClassMap.values()) {
dos.writeUTF(classFileDef.getClassName());// 写类名称
dos.writeInt(classFileDef.getData().length);// 字节码的长度
dos.write(classFileDef.getData());// 字节码
dos.writeUTF(classFileDef.getClassName()); // Write class name
dos.writeInt(classFileDef.getData().length); // Write bytecode length
dos.write(classFileDef.getData()); // Write bytecode
}
dos.flush();
//设置状态,准备更新
// Set state to ready for update
HotSwapManager.updateBytes = baos.toByteArray();
HotSwapManager.exception = null;
//更新
// Apply update
loadHotSwapAgent();
if (HotSwapManager.exception != null) {
for (ClassFileDef classFileDef : updateClassMap.values()) {
logger.error("类[{}]热更新失败", classFileDef.getClassName());
logger.error("Hot-swap failed for class [{}]", classFileDef.getClassName());
}
logger.error("热更新失败原因:", HotSwapManager.exception);
logger.error("Hot-swap failed: ", HotSwapManager.exception);
return;
}
for (ClassFileDef classFileDef : updateClassMap.values()) {
logger.info("类[{}]热更新成功", classFileDef.getClassName());
logger.info("Hot-swap successful for class [{}]", classFileDef.getClassName());
}
long end = TimeUtils.currentTimeMillis();
logger.info("本次热更新总耗时:[{}]毫秒", end - start);
logger.info("Hot-swap total time: [{}]ms", end - start);
// 更新成功,保存更新记录
// Update successful; save update record
classFileDefMap.putAll(updateClassMap);
// 删除所有被更新过的java和class文件
// Delete all updated .java and .class files
for (ClassFileDef def : updateClassMap.values()) {
FileUtils.deleteFile(new File(def.getPath()));
}
@@ -187,13 +187,13 @@ public class HotSwapServiceMBean implements IHotSwapServiceMBean {
private void loadHotSwapAgent() throws Exception {
VirtualMachine vm = null;
try {
// 获取运行当前这个类的jvm的pid
// Get the PID of the JVM running this class
String pid = ManagementFactory.getRuntimeMXBean().getName().split("@")[0];
// attach到这个pid建立通信管道,让jvm加载agent
// Attach to the PID to establish a communication pipe and load the agent
vm = VirtualMachine.attach(pid);
vm.loadAgent(HOT_SWAP_AGENT);
} catch (AgentInitializationException | AgentLoadException | AttachNotSupportedException | IOException e) {
logger.error("热更新开启VirtualMachine失败", e);
logger.error("Hot-swap: failed to attach VirtualMachine", e);
throw e;
} finally {
try {
@@ -201,7 +201,7 @@ public class HotSwapServiceMBean implements IHotSwapServiceMBean {
vm.detach();
}
} catch (IOException e) {
logger.error("热更新关闭vm失败", e);
logger.error("Hot-swap: failed to detach VM", e);
}
}
}
@@ -9,16 +9,16 @@ import javax.management.MXBean;
public interface IHotSwapServiceMBean {
/**
* 热更新相对于项目路径的文件
* Hot-swap a file specified as a path relative to the project root
*
* @param relativePath 相对路径
* @param relativePath relative path
*/
void hotSwapByRelativePath(String relativePath);
/**
* 热更新绝对路径的文件
* Hot-swap a file specified by absolute path
*
* @param absolutePath 热更新文件的绝对路径
* @param absolutePath absolute path of the file to hot-swap
*/
void hotSwapByAbsolutePath(String absolutePath);
@@ -20,7 +20,7 @@ import java.lang.instrument.ClassDefinition;
* Hotswap java class
* <p>
* EN: Prefer using simple Javassist for hot updates, followed by Byte Buddy hot updates
* CN: 优先使用简单的Javassist做热更新,因为Byte Buddy使用了更为复杂的ASM,spring boot web项目中会优先使用Byte Buddy热更新
* Prefer Javassist for hot-swapping (simpler); Byte Buddy uses more complex ASM. Spring Boot web projects prefer Byte Buddy for hot-swapping
*
* @author godotg
*/
@@ -31,13 +31,13 @@ public abstract class HotSwapUtils {
/**
* need to add JVM startup parameters: -Djdk.attach.allowAttachSelf=true
* <p>
* JVM的启动参数,jdk11过后默认JVM不允许连接自己,所以需要自己添加 -Djdk.attach.allowAttachSelf=true 启动参数
* JVM startup parameter: since JDK 11, the JVM does not allow self-attachment by default. Add -Djdk.attach.allowAttachSelf=true to enable it
* <p>
* note: 需要配置 JAVA_HOME 环境变量,如果没有配置这个环境变量可能会导致未知异常
* note: 使用 -cp 或者 -Djava.ext.dirs 参数可能会导致热更新未知异常,推荐使用maven-shade-plugin或者spring-boot-maven-plugin将工程打包进一个jar里。
* note: 使用 -cp热更时会有概率出现java.lang.NoClassDefFoundError,其中多现于Lambda表达式和静态Enum的类中
* note: JAVA_HOME environment variable must be configured; unknown errors may occur without it
* note: Using -cp or -Djava.ext.dirs may cause unknown hot-swap errors; recommend packaging with maven-shade-plugin or spring-boot-maven-plugin
* note: Using -cp may occasionally cause java.lang.NoClassDefFoundError, especially in Lambda expressions and static Enum classes
*
* @param bytes .class结尾的字节码文件
* @param bytes bytecode from a .class file
*/
public static synchronized void hotswapClass(byte[] bytes) {
if (ArrayUtils.isEmpty(bytes)) {
@@ -12,17 +12,17 @@ import java.io.IOException;
*/
public class ApplicationTest {
// 热更新教程,需要添加JVM参数,-Djdk.attach.allowAttachSelf=true,如果不加这个参数将使用Byte Buddy热更新替代Javassist热更新
// 使用Javassist热更新更加的轻量,如果Javassist热更新失败,则会自动使用Byte Buddy做热更新
// Hot-swap tutorial: add JVM arg -Djdk.attach.allowAttachSelf=true; without it, Byte Buddy hot-swap is used instead of Javassist
// Javassist hot-swap is lighter; if it fails, Byte Buddy is used automatically
@Test
public void startHotSwapTest() throws IOException {
// 热更新限制,不能为需要热更新的类添加或减少成员函数和成员变量,只能修改函数内部的逻辑
// Hot-swap limitation: cannot add or remove methods/fields; only internal logic may be changed
var test = new HotswapClass();
// 没有热更新的输出
// Output before hot-swap
test.print();
// 随便修改print方法,然后编译成为一个需要热更新的class文件
// Modify the print method, then compile it to a .class file to be hot-swapped
HotSwapUtils.hotswapClass(IOUtils.toByteArray(ClassUtils.getFileFromClassPath("HotswapClass.class")));
// 热更新之后的输出
// Output after hot-swap
test.print();
}
@@ -11,7 +11,7 @@ public class HotswapClass {
private static final Logger logger = LoggerFactory.getLogger(HotswapClass.class);
public void print() {
logger.info("没有热更新的输出内容");
logger.info("No hot-swap output content");
}
}