目前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的吉祥物被選為幽靈,如下圖。
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsICM38FdsYkRGZkRG9lcvx2bjxiNx8VZ6l2csknVtRGcK5mYsJ0MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL4MTN0UjN0ETMyEzNwEjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
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();
}
}