bytecode kit for java.
- 之前的Arthas里的字节码增强,是通过asm来处理的,代码逻辑不好修改,理解困难
- 基于ASM提供更高层的字节码处理能力,面向诊断/APM领域,不是通用的字节码库
- ByteKit期望能提供一套简洁的API,让开发人员可以比较轻松的完成字节码增强
| 功能 | 函数Enter/Exit注入点 | 绑定数据 | inline | 防止重复增强 | 避免装箱/拆箱开销 | origin调用替换 | @ExceptionHandler |
|---|---|---|---|---|---|---|---|
| ByteKit | @AtEnter @AtExit @AtExceptionExit @AtFieldAccess @AtInvoke@AtInvokeException@AtLine@AtSyncEnter@AtSyncExit@AtThrow |
this/args/return/throw field locals 子调用入参/返回值/子调用异常 line number |
✓ | ✓ | ✓ | ✓ | ✓ |
| ByteBuddy | @OnMethodEnter@OnMethodExit@OnMethodExit#onThrowable() |
this/args/return/throw field locals |
✓ | ✗ | ✓ | ✓ | ✓ |
| 传统AOP | EnterExitException |
this/args/return/throw | ✗ | ✗ | ✗ | ✗ | ✗ |
@AtEnter函数入口@AtExit函数退出@AtExceptionExit函数抛出异常@AtFieldAccess访问field@AtInvoke在method里的子函数调用@AtInvokeException在method里的子函数调用抛出异常@AtLine在指定行号@AtSyncEnter进入同步块,比如synchronized块@AtSyncExit退出同步块@AtThrow代码里显式throw异常点
-
@Binding.Thisthis对象 -
@Binding.ClassClass对象 -
@Binding.Method函数调用的 Method 对象 -
@Binding.MethodName函数的名字 -
@Binding.MethodDesc函数的desc -
@Binding.Return函数调用的返回值 -
@Binding.Throwable函数里抛出的异常 -
@Binding.Args函数调用的入参 -
@Binding.ArgNames函数调用的入参的名字 -
@Binding.LocalVars局部变量 -
@Binding.LocalVarNames局部变量的名字 -
@Binding.Fieldfield对象属性字段 -
@Binding.InvokeArgsmethod里的子函数调用的入参 -
@Binding.InvokeReturnmethod里的子函数调用的返回值 -
@Binding.InvokeMethodNamemethod里的子函数调用的名字 -
@Binding.InvokeMethodOwnermethod里的子函数调用的类名 -
@Binding.InvokeMethodDeclarationmethod里的子函数调用的desc -
@Binding.Line行号 -
@Binding.Monitor同步块里监控的对象
@ExceptionHandler在插入的增强代码,可以用try/catch块包围起来
增强的代码 和 异常处理代码都可以通过 inline技术内联到原来的类里,达到最理想的增强效果。
通常,我们要增强一个类,就想要办法在函数前后插入一个static的回调函数,但这样子局限太大。那么有没有更灵活的方式呢?
比如有一个 hello() 函数:
public String hello(String str) {
return "hello " + str;
}我们想对它做增强,那么可以编写下面的代码:
public String hello(String str) {
System.out.println("before");
Object value = InstrumentApi.invokeOrigin();
System.out.println("after, result: " + value);
return object;
}增强后的结果是:
public String hello(String str) {
System.out.println("before");
Object value = "hello " + str;
System.out.println("after, result: " + value);
return object;
}这种方式可以随意插入代码,非常灵活。
参考增强Dubbo Filter的示例: [查看源文件]
以ByteKitDemo.java为例说明,[查看源文件]。
- 在下面的 SampleInterceptor 时定义了要注入
@AtEnter/@AtExit/@AtExceptionExit三个地方, - 用
@Binding绑定了不同的数据 - 在
@AtEnter里配置了inline = true,则说明插入的SampleInterceptor#atEnter函数本身会被inline掉 - 配置了
suppress = RuntimeException.class和suppressHandler = PrintExceptionSuppressHandler.class,说明插入的代码会被try/catch包围
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);
}
}在上面的 @AtEnter配置里,生成的代码会被 try/catch 包围,那么具体的内容是在PrintExceptionSuppressHandler里
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();
}
}原始的Sample类是:
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;
}
}增强后的字节码,再反编译:
package com.example;
public static class ByteKitDemo.Sample {
private int exceptionCount = 0;
public String hello(String string, boolean bl) {
try {
String string2 = "(Ljava/lang/String;Z)Ljava/lang/String;";
String string3 = "hello";
Object[] arrobject = new Object[]{string, new Boolean(bl)};
Class<ByteKitDemo.Sample> class_ = ByteKitDemo.Sample.class;
ByteKitDemo.Sample sample = this;
System.out.println("atEnter, args[0]: " + arrobject[0]);
}
catch (RuntimeException runtimeException) {
Class<ByteKitDemo.Sample> class_ = ByteKitDemo.Sample.class;
RuntimeException runtimeException2 = runtimeException;
System.out.println("exception handler: " + class_);
runtimeException2.printStackTrace();
}
try {
String string4;
void str;
void exception;
if (exception != false) {
++this.exceptionCount;
throw new RuntimeException("test exception, str: " + (String)str);
}
String string5 = string4 = "hello " + (String)str;
System.out.println("atExit, returnObject: " + string5);
return string4;
}
catch (RuntimeException runtimeException) {
int n = this.exceptionCount;
RuntimeException runtimeException3 = runtimeException;
System.out.println("atExceptionExit, ex: " + runtimeException3.getMessage() + ", field exceptionCount: " + n);
throw runtimeException;
}
}
}deploy到远程仓库:
mvn clean deploy -DskipTests -P releaseApache License V2