当前位置: 首页 > news >正文

SkyWalking-3--Java Agent开发和集成示例

1、Java Agent的开发步骤说明

1、定义Agent类

创建一个包含premain方法的类,即:Agent类。
该类中的premain方法会在JVM启动时被调用,该方法的主要作用是注册类转换器ClassTransformer对象。

代码示例:

public class MyAgent {// JVM 启动时自动调用public static void premain(String args, Instrumentation inst) {// 添加类转换器inst.addTransformer(new MyClassTransformer());}
}

说明:

  • premain是JVM启动时加载Agent的入口方法。
  • Instrumentation inst参数由JVM提供,是操作类定义的核心工具。
  • 通过inst.addTransformer(…)注册一个转换器,相当于告诉JVM:“后续加载的类,你可以让我先处理一下”。

2、实现ClassFileTransformer

在ClassFileTransformer的transform方法中,动态修改目标类的字节码

代码示例:

public class MyClassTransformer implements ClassFileTransformer {@Overridepublic byte[] transform(ClassLoader loader, String className,Class<?> classBeingRedefined,ProtectionDomain protectionDomain,byte[] classfileBuffer) {// 1、判断是否是目标类if (!"target/MyService".equals(className)) {return null; // 不修改}// 2、使用ASM等工具修改字节码byte[] modified = modifyBytecode(classfileBuffer);// 3、返回修改后的字节码return modified;}
}

说明:

  • transform()方法是字节码修改的“主战场”。
  • 当JVM即将定义一个类(ClassLoader.defineClass)之前,会先调用所有注册的transform方法。
  • 你可以在其中:
    • 读取原始字节码(classfileBuffer)
    • 使用ASM / Javassist / Byte Buddy修改字节码
    • 返回修改后的byte[]
    • 返回null表示不修改

3、打包Agent JAR

在MANIFEST.MF中指定Premain-Class。

MANIFEST.MF示例:

Manifest-Version: 1.0
Premain-Class: your.package.MyAgent
Can-Redefine-Classes: true
Can-Retransform-Classes: true

说明:

  • Premain-Class:必须,指定包含premain方法的类
  • Can-Redefine-Classes:允许重新定义类(如Arthas使用)
  • Can-Retransform-Classes:允许重新转换类(关键!如果你要修改已加载的类,必须开启)

4、启动应用时加载Agent

使用-javaagent参数指定Agent JAR。

bash示例:

java -javaagent:my-agent.jar -jar my-app.jar

5、JVM调用流程

  • 类加载时触发:当JVM加载类(ClassLoader.defineClass)时,会调用所有注册的ClassFileTransformer。
  • 字节码修改:transform方法会修改源字节码文件,并返回新的字节数组被JVM使用,作为新的类定义。
  • 类缓存:修改后的类会被缓存,后续直接使用修改后的版本。

总结:
Java Agent通过premain注册ClassFileTransformer,在类被JVM加载的瞬间修改其字节码,从而实现无侵入的代码增强

2、Java Agent具体开发示例

1、目标

假设我们有一个MyService类。我们希望不修改源码,通过Java Agent在hello()方法执行前后自动插入日志。

Service代码示例:

public class MyService {public void hello() {System.out.println("Hello, World!");}
}

日志效果要求:

BEFORE: hello()
Hello, World!
AFTER: hello()

2、实现

1、方式一:使用ASM+Java Agent
1、项目结构
java-agent-demo/
├── pom.xml
├── src/
│   ├── main/
│   │   ├── java/
│   │   │   └── agent/
│   │   │       ├── MyAgent.java
│   │   │       └── LoggingClassVisitor.java
│   │   └── resources/
│   │       └── META-INF/
│   │           └── MANIFEST.MF
│   └── test/
│       └── java/
│           └── MyService.java
2、pom.xml(Maven)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>cn.demo</groupId><artifactId>java-agent-demo</artifactId><version>1.0.0</version><packaging>jar</packaging><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties><dependencies><dependency><groupId>org.ow2.asm</groupId><artifactId>asm</artifactId><version>9.6</version></dependency><dependency><groupId>org.ow2.asm</groupId><artifactId>asm-commons</artifactId><version>9.6</version></dependency></dependencies><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-jar-plugin</artifactId><version>3.3.0</version><configuration><archive><manifestFile>src/main/resources/META-INF/MANIFEST.MF</manifestFile></archive></configuration></plugin></plugins></build>
</project>

注意:
一定要添加之间的这一段。

3、src/main/resources/META-INF/MANIFEST.MF

示例:

Manifest-Version: 1.0
Premain-Class: agent.MyAgent
Can-Redefine-Classes: true
Can-Retransform-Classes: true

注意:
每行后面不能有空格,最后一行要换行

4、agent/MyAgent.java

代码示例:

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;public class MyAgent {public static void premain(String agentArgs, Instrumentation inst) {System.out.println("Java Agent 已加载(ASM 版)");inst.addTransformer(new ClassFileTransformer() {   // 注册ClassFileTransformer类@Overridepublic byte[] transform(ClassLoader loader,      // transform方法修改字节码String className,Class<?> classBeingRedefined,ProtectionDomain protectionDomain,byte[] classfileBuffer) throws IllegalClassFormatException {// 只处理 MyService 类if (!"com/example/MyClass".equals(className)) {return null;}System.out.println("正在转换类: " + className);ClassReader cr = new ClassReader(classfileBuffer);ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS);ClassVisitor cv = new LoggingClassVisitor(cw);cr.accept(cv, ClassReader.EXPAND_FRAMES);return cw.toByteArray();}});}
}
5、agent/LoggingClassVisitor.java

代码示例:

import org.objectweb.asm.*;
import org.objectweb.asm.commons.AdviceAdapter;
import static org.objectweb.asm.Opcodes.*;public class LoggingClassVisitor extends ClassVisitor {public LoggingClassVisitor(ClassVisitor cv) {super(Opcodes.ASM9, cv);}@Overridepublic MethodVisitor visitMethod(int access, String name, String descriptor,String signature, String[] exceptions) {MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);if (mv != null && name.equals("hello")) {return new AdviceAdapter(Opcodes.ASM9, mv, access, name, descriptor) {@Overrideprotected void onMethodEnter() {// 在hello()方法执行前插入日志:System.out.println("BEFORE: hello()");getStatic(Type.getType(System.class), "out", Type.getType(java.io.PrintStream.class));push("BEFORE: hello()");invokeVirtual(Type.getType(java.io.PrintStream.class),new Method("println", "(Ljava/lang/String;)V"));}@Overrideprotected void onMethodExit(int opcode) {// 在方法返回前插入日志:System.out.println("AFTER: hello()");                    if (opcode != ATHROW) {getStatic(Type.getType(System.class), "out", Type.getType(java.io.PrintStream.class));push("AFTER: hello()");invokeVirtual(Type.getType(java.io.PrintStream.class),new Method("println", "(Ljava/lang/String;)V"));}}};}return mv;}
}
6、MyService.java(测试类)

代码示例:

// src/test/java/MyService.java
public class MyService {public static void main(String[] args) {new MyService().hello();}public void hello() {System.out.println("Hello, World!");}
}
2、方式二:使用Byte Buddy+Java Agent(更简单)

步骤同方法一一样,只需替依赖和MyAgent.java。

依赖示例:

<dependency><groupId>net.bytebuddy</groupId><artifactId>byte-buddy</artifactId><version>1.14.0</version>
</dependency>
<dependency><groupId>net.bytebuddy</groupId><artifactId>byte-buddy-agent</artifactId><version>1.14.0</version>
</dependency>

代码示例:

// agent/MyAgent.java
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.matcher.ElementMatchers;import java.lang.instrument.Instrumentation;public class MyAgent {public static void premain(String agentArgs, Instrumentation inst) {System.out.println("Java Agent 已加载(Byte Buddy 版)");new AgentBuilder.Default().type(ElementMatchers.named("MyService")) // 拦截类.transform((builder, typeDescription, classLoader, module) ->builder.method(ElementMatchers.named("hello")) // 拦截方法.intercept(Advice.to(LoggingAdvice.class)) // 插入逻辑).installOn(inst);}@SuppressWarnings("unused")public static class LoggingAdvice {@Advice.OnMethodEnterstatic void enter(@Advice.Origin String method) {   // 方法进入前执行System.out.println("BEFORE: " + method);}@Advice.OnMethodExitstatic void exit(@Advice.Origin String method) {    // 方法进入后执行System.out.println("AFTER: " + method);}}
}

3、编译和运行步骤

1、编译打包

bash示例:

mvn clean package

生成:target/java-agent-demo-1.0.0.jar

2、运行测试程序(带上Agent)

bash示例:

java -javaagent:target/java-agent-demo-1.0.0.jar \-cp target/test-classes \MyService

3、输出结果

BEFORE: hello()
Hello, World!
AFTER: hello()

4、验证Agent是否生效

1、如果不加 -javaagent,不会打印 BEFORE/AFTER。
2、加上后就会插入日志,说明字节码已被修改。

4、ASM vs Byte Buddy Agent

在这里插入图片描述

向阳前行,Dare To Be!!!

http://www.dtcms.com/a/323577.html

相关文章:

  • AI智能编程工具汇总
  • ComfyUI版本更新---解决ComfyUI的节点不兼容问题
  • MySQL 主备(Master-Slave)复制 的搭建
  • SOLIDWORKS 2025对工具栏等进行了重新布局和优化
  • GoEnhance AI-AI视频风格转换工具
  • gRPC 全面解析与实战 —— 从原理到同步/异步开发全攻略
  • Linux系统编程——进程地址空间
  • GM3568JHF:FPGA+ARM异构开发板环境搭建教程
  • 嵌入式学习day23-shell命令
  • Qdrant Filtering:must / should / must_not 全解析(含 Python 实操)
  • 【Python 高频 API 速学 ②】
  • 【线程池】压测确定线程池合适的参数
  • 【js】判断异步函数的返回值要加await
  • 使用LangGraph从零构建多智能体AI系统:实现智能协作的完整指南
  • 计算机系统设计中都有什么任务~计算密集~IO密集~逻辑密集等
  • 提示条贴合右侧边栏
  • java web项目入门了解
  • 天地图,cesium,leaflet
  • java练习题:数字位数
  • Windows下使用PyInstaller打包PyQt项目
  • 第15届蓝桥杯Scratch图形化省赛中级组2024年8月24日真题
  • 4深度学习Pytorch-神经网络--损失函数(sigmoid、Tanh、ReLU、LReLu、softmax)
  • Linux-JSON Schema
  • Java类和对象课上练习题目设计
  • LLM 的向量的方向表示语义,向量长度表示什么
  • Docker容器lnmp平台部署discuz论坛
  • 工具类-高效集合差异计算工具DiffWrapper
  • visual studio 无明显错误,但是无法编译成功解决—仙盟创梦IDE
  • C++入门自学Day7-- String类的自实现
  • Adapting Vision-Language Models Without Labels A Comprehensive Survey