目前Java字节码生成框架大致有ASM、Javassist和byte buddy
BECL不常用,ByteKit是Arthas中使用的字节码工具集
1 字节码生成框架
1.1 ASM
1.1.1 介绍
ASM官网 介绍如下:
ASM是一个通用的Java字节码操作和分析框架。它能够以二进制形式修改已有的类或是动态生成类。
ASM提供了一些常见的字节码转换和分析算法,可以根据这些算法构建定制的复杂转换和代码分析工具。
ASM提供了与其他Java字节码框架类似的功能,但侧重于性能。因为它的设计和实现是尽可能的小和快,所以它非常适合在动态系统中使用(当然也可以在静态方式中使用,例如在编译器中)
源码 ,当前最新版本是9.2
1.1.2 典型案例
ASM在许多项目中使用,如实现动态代理的CGLIB,包括但不限于如下:
- the OpenJDK, to generate the lambda call sites, and also in the Nashorn compiler,
- the Groovy compiler and the Kotlin compiler,
- Cobertura and Jacoco, to instrument classes in order to measure code coverage,
- CGLIB, to dynamically generate proxy classes (which are used in other projects such as Mockito and EasyMock),
- Gradle, to generate some classes at runtime.
- Arthas(阿尔萨斯)Arthas增强功能的核心是Enhancer和AdviceWeaver这两个类,对方法进行Aop织入,达到watch,trace等效果。
1.2 Javassist
javassist是jboss的一个子项目,其主要的优点,在于简单,而且快速。
源码, 当前最新版本是:3.28.0-GA
Javassist (Java编程助手)使Java字节码操作变得简单。它是一个用于在Java中编辑字节码的类库; 它使Java程序能够在运行时定义一个新类,并在JVM加载类文件时修改类文件。
与其他类似的字节码编辑器不同,Javassist提供了两个级别的API:
- 源代码级别: 可以编辑类文件,而不需要了解Java字节码的规范。整个API直接使用java编码的形式进行设计。甚至可以以源文本【source text】的形式指定插入的字节码;Javassist动态地编译它。
- 字节码级别:字节码级API允许用户像其他编辑器一样直接编辑类文件。
1.3 bytebuddy
1.3.1 介绍
Byte Buddy是一个代码生成和操作库,用于在Java应用程序运行时创建和修改Java类,无需编译器的帮助。除了Java类库附带的代码生成实用程序外,Byte Buddy允许创建任意类,而且不限于实现创建运行时代理的接口。此外,Byte Buddy提供了一个方便的API,可以使用Java代理或在构建过程中手动更改类。
要使用Byte Buddy,不需要理解Java字节代码或类文件格式。相比之下,Byte Buddy的API旨在编写简洁且易于理解的代码。
API被设计成尽可能非侵入性,因此,Byte Buddy不会在它创建的类中留下任何痕迹。因此,生成的类可以存在,而不需要类路径上的Byte Buddy。因为这个特性,Byte Buddy的吉祥物被选为幽灵,如下图。
Byte Buddy是用Java 5编写的,但支持为任何Java版本生成类。Byte Buddy是一个轻量级的库,只依赖于Java字节代码解析器库ASM的访问者API, ASM本身不需要任何进一步的依赖。
1.3.2 典型案例
bytebuddy在许多项目中使用,包括但不限于如下:
- Apache SkyWalking: 分布式系统的应用程序性能监视工具,用来增强agent的监控点的类,实现性能监控指标。
1.4 BCEL
Commons BCEL
字节代码工程库(Apache Commons BCEL™)旨在为用户提供一种方便的方法来分析、创建和操作(二进制)Java类文件(以.class结尾的文件)。类由对象表示,对象包含给定类的所有符号信息:特别是方法、字段和字节代码指令。
这些对象可以从现有文件中读取,由程序转换(例如,运行时的类加载器),然后再次写入文件。一个更有趣的应用程序是在运行时从头创建类。如果您想了解Java虚拟机(JVM)和Java .class文件的格式,字节代码工程库(BCEL)可能也很有用。
BCEL包含一个名为JustIce的字节代码验证器,它通常提供比标准JVM消息更好的关于代码错误的信息。
当前的版本是:
<dependency>
<groupId>org.apache.bcel</groupId>
<artifactId>bcel</artifactId>
<version>6.5.0</version>
</dependency>
1.5 ByteKit
ByteKit:bytecode kit for java,在Arthas中使用的字节码工具集,目标是:
- 之前的Arthas里的字节码增强,是通过asm来处理的,代码逻辑不好修改,理解困难
- 基于ASM提供更高层的字节码处理能力,面向诊断/APM领域,不是通用的字节码库
- ByteKit期望能提供一套简洁的API,让开发人员可以比较轻松的完成字节码增强
功能 | 函数Enter/Exit注入点 | 绑定数据 | inline | 防止重复增强 | 避免装箱/拆箱开销 | origin调用替换 | |
---|---|---|---|---|---|---|---|
ByteKit | | this/args/return/throw field locals 子调用入参/返回值/子调用异常 line number | ✓ | ✓ | ✓ | ✓ | ✓ |
ByteBuddy | | this/args/return/throw field locals | ✓ | ✗ | ✓ | ✓ | ✓ |
传统AOP | | this/args/return/throw | ✗ | ✗ | ✗ | ✗ | ✗ |
2. 使用代码示例
2.1 asm示例
实现后的效果仅仅是记录方法的执行时间:
public static void show() {
long l = System.currentTimeMillis();
System.out.println("Hello World");
long l2 = System.currentTimeMillis() - l;
System.out.println("\u65b9\u6cd5\u6267\u884c\u8017\u65f6\u662f:" + l2 + " ms");
}
执行结果是:
Hello World
方法执行耗时是:0 ms
源码示例
import com.alibaba.bytekit.utils.AgentUtils;
import com.alibaba.bytekit.utils.Decompiler;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import java.io.IOException;
import java.lang.instrument.UnmodifiableClassException;
class ASMDemo {
public static void main(String[] args) throws IOException, UnmodifiableClassException {
ClassReader cr = new ClassReader(User.class.getName());
ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS);
ClassVisitor cv = new MyClassVisitor(cw);
cr.accept(cv, Opcodes.ASM5);
// 获取生成的class文件对应的二进制流
byte[] bytes = cw.toByteArray();
//将二进制流写到out/下
// FileOutputStream fos = new FileOutputStream("F:/tmp/User.class");
// fos.write(code);
// fos.close();
//查看增强后反编译的结果
System.out.println(Decompiler.decompile(bytes));
// 通过 reTransform 增强类
AgentUtils.reTransform(User.class, bytes);
User.show();
}
}
class User {
public static void show(){
System.out.println("Hello World");
}
}
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
public class MyClassVisitor extends ClassVisitor {
public MyClassVisitor(final ClassVisitor cv) {
super(Opcodes.ASM9, cv);
}
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
if (cv != null) {
cv.visit(version, access, name, signature, superName, interfaces);
}
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
//如果methodName是show,则返回自定义的MyMethodVisitor
if ("show".equals(name)) {
MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
return new MyMethodVisitor(mv);
}
if (cv != null) {
return cv.visitMethod(access, name, desc, signature, exceptions);
}
return null;
}
}
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
public class MyMethodVisitor extends MethodVisitor implements Opcodes {
public MyMethodVisitor(MethodVisitor mv) {
super(Opcodes.ASM9, mv);
}
@Override
public void visitCode() {
//方法体内开始时调用
mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
mv.visitVarInsn(LSTORE, 0);
super.visitCode();
}
@Override
public void visitInsn(int opcode) {
//每执行一个指令都会调用
if (opcode == Opcodes.RETURN) {
mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
mv.visitVarInsn(LLOAD, 0);
mv.visitInsn(LSUB);
mv.visitVarInsn(LSTORE, 2);
Label l3 = new Label();
mv.visitLabel(l3);
mv.visitLineNumber(11, l3);
mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
mv.visitInsn(DUP);
mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
mv.visitLdcInsn("== method cost time = ");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
mv.visitVarInsn(LLOAD, 2);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(J)Ljava/lang/StringBuilder;", false);
mv.visitLdcInsn(" ==");
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
}
super.visitInsn(opcode);
}
}
maven依赖
<asm.version>9.0</asm.version>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>${asm.version}</version>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-commons</artifactId>
<version>${asm.version}</version>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-util</artifactId>
<version>${asm.version}</version>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-analysis</artifactId>
<version>${asm.version}</version>
</dependency>
2.2 bytebuddy示例
maven依赖
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.10.18</version>
</dependency>
源码示例
class ByteBuddyTest {
public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException {
Class dynamicType = new ByteBuddy()
.subclass(Dog.class)
.method(ElementMatchers.named("say"))
.intercept(MethodDelegation.to(MyServiceInterceptor.class))
.make()
.load(ByteBuddyTest.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
.getLoaded();
Object s = dynamicType.newInstance();
System.out.println(((Dog)s).say(" 欢迎测试bytebuddy"));
}
}
public class MyServiceInterceptor {
@RuntimeType
public static String intercept(
//用于调用父类版本的方法
@SuperCall Callable<String> zuper,
@AllArguments Object[] allArguments,
//被拦截的原始方法
@Origin Method method
) throws Exception {
System.out.println("intercept:拦截" + method.getName());
StringBuffer sb = new StringBuffer();
Arrays.stream(allArguments).forEach(arg -> sb.append(arg).append(","));
System.out.println("参数是:" + sb.toString());
String res = zuper.call();
System.out.println("执行结果是:" + res);
return res;
}
}
public class Dog {
public String say(String msg){
return "汪汪汪"+msg;
}
}
2.3 Bytekit示例
maven依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>bytekit-core</artifactId>
<version>0.0.4</version>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy-agent</artifactId>
<version>1.10.18</version>
</dependency>
<dependency>
<groupId>org.benf</groupId>
<artifactId>cfr</artifactId>
<version>0.150</version>
</dependency>
源码示例
import java.util.List;
import java.util.concurrent.TimeUnit;
import com.alibaba.deps.org.objectweb.asm.tree.ClassNode;
import com.alibaba.deps.org.objectweb.asm.tree.MethodNode;
import com.alibaba.bytekit.asm.MethodProcessor;
import com.alibaba.bytekit.asm.binding.Binding;
import com.alibaba.bytekit.asm.interceptor.InterceptorProcessor;
import com.alibaba.bytekit.asm.interceptor.annotation.AtEnter;
import com.alibaba.bytekit.asm.interceptor.annotation.AtExceptionExit;
import com.alibaba.bytekit.asm.interceptor.annotation.AtExit;
import com.alibaba.bytekit.asm.interceptor.annotation.ExceptionHandler;
import com.alibaba.bytekit.asm.interceptor.parser.DefaultInterceptorClassParser;
import com.alibaba.bytekit.utils.AgentUtils;
import com.alibaba.bytekit.utils.AsmUtils;
import com.alibaba.bytekit.utils.Decompiler;
public class ByteKitDemo {
public static class Sample {
private int exceptionCount = 0;
public String hello(String str, boolean exception) {
if (exception) {
exceptionCount++;
throw new RuntimeException("test exception, str: " + str);
}
return "hello " + str;
}
}
public static class PrintExceptionSuppressHandler {
@ExceptionHandler(inline = true)
public static void onSuppress(@Binding.Throwable Throwable e, @Binding.Class Object clazz) {
System.out.println("exception handler: " + clazz);
e.printStackTrace();
}
}
public static class SampleInterceptor {
@AtEnter(inline = true, suppress = RuntimeException.class, suppressHandler = PrintExceptionSuppressHandler.class)
public static void atEnter(@Binding.This Object object,
@Binding.Class Object clazz,
@Binding.Args Object[] args,
@Binding.MethodName String methodName,
@Binding.MethodDesc String methodDesc) {
System.out.println("atEnter, args[0]: " + args[0]);
}
@AtExit(inline = true)
public static void atExit(@Binding.Return Object returnObject) {
System.out.println("atExit, returnObject: " + returnObject);
}
@AtExceptionExit(inline = true, onException = RuntimeException.class)
public static void atExceptionExit(@Binding.Throwable RuntimeException ex,
@Binding.Field(name = "exceptionCount") int exceptionCount) {
System.out.println("atExceptionExit, ex: " + ex.getMessage() + ", field exceptionCount: " + exceptionCount);
}
}
public static void main(String[] args) throws Exception {
AgentUtils.install();
// 解析定义的 Interceptor类 和相关的注解
DefaultInterceptorClassParser interceptorClassParser = new DefaultInterceptorClassParser();
List<InterceptorProcessor> processors = interceptorClassParser.parse(SampleInterceptor.class);
// 加载字节码
ClassNode classNode = AsmUtils.loadClass(Sample.class);
// 对加载到的字节码做增强处理
for (MethodNode methodNode : classNode.methods) {
if (methodNode.name.equals("hello")) {
MethodProcessor methodProcessor = new MethodProcessor(classNode, methodNode);
for (InterceptorProcessor interceptor : processors) {
interceptor.process(methodProcessor);
}
}
}
// 获取增强后的字节码
byte[] bytes = AsmUtils.toBytes(classNode);
// 查看反编译结果
System.out.println(Decompiler.decompile(bytes));
// 通过 reTransform 增强类
AgentUtils.reTransform(Sample.class, bytes);
// 启动Sample,不断执行
final Sample sample = new Sample();
Thread t = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; ++i) {
try {
TimeUnit.SECONDS.sleep(3);
String result = sample.hello("" + i, (i % 3) == 0);
System.out.println("call hello result: " + result);
} catch (Throwable e) {
// ignore
System.out.println("call hello exception: " + e.getMessage());
}
}
}
});
t.start();
System.in.read();
}
}