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 {
@Override
public 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类
@Override
public 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);
}
@Override
public 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) {
@Override
protected 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"));
}
@Override
protected 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.OnMethodEnter
static void enter(@Advice.Origin String method) { // 方法进入前执行
System.out.println("BEFORE: " + method);
}
@Advice.OnMethodExit
static 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!!!
更多推荐
所有评论(0)