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

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.xdnf.cn/news/17329.html

相关文章:

  • 在JVM调优时,你关注哪些指标?
  • Arm Development Studio 安全通告:CVE-2025-7427
  • 数据结构day06
  • 【工具变量】全国省级农业保险保费收入与赔付支出数据更新(2001-2023年)
  • JVM常用参数有哪些?
  • 虚拟机Ubuntu重启发现找不到共享文件夹
  • Flutter Listview的基本使用
  • Cursor手机版:一半是神,一半是坑
  • 【能碳建设2】把“能碳计算”做成可配置、可演示的系统
  • 【线性代数】线性方程组与矩阵——(2)矩阵与线性方程组的解
  • 深入理解 @Schema 注解:让你的 API 文档自动 “说话”
  • 消息队列核心功能和消息队列做异步的优势
  • NX二次开发——面有关的函数
  • Datawhale AI夏令营学习
  • Kubernetes 集群密钥与机密管理方案对比分析:Vault、Sealed Secrets 与 AWS KMS
  • C++方向知识汇总(一)
  • 可泛化双手操作机器人基准测试:CVPR 2025 MEIS 研讨会 RoboTwin 双臂协作挑战赛
  • SwiftUI 登录页面键盘约束冲突与卡顿优化全攻略
  • 大语言模型提示工程与应用:提示工程-提升模型准确性与减少偏见的方法
  • node.js 零基础入门
  • mfc按钮点击事件没有触发,且程序卡死
  • VGMP状态机解析
  • 【GitHub小娱乐】GitHub个人主页ProFile美化
  • 交 换
  • 分享一个基于Spark的眼科疾病临床数据可视化分析与应用研究Hadoop基于Vue和Echarts的眼科疾病统计数据交互式可视化系统的设计与实现
  • HarvardX TinyML小笔记1(番外2:神经网络)
  • FreeRTOS学习笔记:任务通知和软件定时器
  • gRPC for C++ 实战全流程 —— 从零搭建到同步/异步服务
  • 第二十八天(cookiesessiontokeny验证)
  • LeetCode 分类刷题:209. 长度最小的子数组