Skip to content

Repository files navigation

ByteKit

JavaCImavenlicenseAverage time to resolve an issuePercentage of issues still open

bytecode kit for java.

目标

  1. 之前的Arthas里的字节码增强,是通过asm来处理的,代码逻辑不好修改,理解困难
  2. 基于ASM提供更高层的字节码处理能力,面向诊断/APM领域,不是通用的字节码库
  3. 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
传统AOPEnter
Exit
Exception
this/args/return/throw

特性

1. 丰富的注入点支持

  • @AtEnter 函数入口
  • @AtExit 函数退出
  • @AtExceptionExit 函数抛出异常
  • @AtFieldAccess 访问field
  • @AtInvoke 在method里的子函数调用
  • @AtInvokeException 在method里的子函数调用抛出异常
  • @AtLine 在指定行号
  • @AtSyncEnter 进入同步块,比如synchronized
  • @AtSyncExit 退出同步块
  • @AtThrow 代码里显式throw异常点

2. 动态的Binding

  • @Binding.This this对象

  • @Binding.Class Class对象

  • @Binding.Method 函数调用的 Method 对象

  • @Binding.MethodName 函数的名字

  • @Binding.MethodDesc 函数的desc

  • @Binding.Return 函数调用的返回值

  • @Binding.Throwable 函数里抛出的异常

  • @Binding.Args 函数调用的入参

  • @Binding.ArgNames 函数调用的入参的名字

  • @Binding.LocalVars 局部变量

  • @Binding.LocalVarNames 局部变量的名字

  • @Binding.Field field对象属性字段

  • @Binding.InvokeArgs method里的子函数调用的入参

  • @Binding.InvokeReturn method里的子函数调用的返回值

  • @Binding.InvokeMethodName method里的子函数调用的名字

  • @Binding.InvokeMethodOwner method里的子函数调用的类名

  • @Binding.InvokeMethodDeclaration method里的子函数调用的desc

  • @Binding.Line 行号

  • @Binding.Monitor 同步块里监控的对象

3. 可编程的异常处理

  • @ExceptionHandler 在插入的增强代码,可以用try/catch块包围起来

4. inline支持

增强的代码 和 异常处理代码都可以通过 inline技术内联到原来的类里,达到最理想的增强效果。

5. invokeOrigin 技术

通常,我们要增强一个类,就想要办法在函数前后插入一个static的回调函数,但这样子局限太大。那么有没有更灵活的方式呢?

比如有一个 hello() 函数:

publicStringhello(Stringstr) {
return"hello " + str;
}

我们想对它做增强,那么可以编写下面的代码:

publicStringhello(Stringstr) {
System.out.println("before");
Objectvalue = InstrumentApi.invokeOrigin();
System.out.println("after, result: " + value);
returnobject;
}

增强后的结果是:

publicStringhello(Stringstr) {
System.out.println("before");
Objectvalue = "hello " + str;
System.out.println("after, result: " + value);
returnobject;
}

这种方式可以随意插入代码,非常灵活。

参考增强Dubbo Filter的示例: [查看源文件]

示例

ByteKitDemo.java为例说明,[查看源文件]。

1. 定义注入点和Binding数据

  • 在下面的 SampleInterceptor 时定义了要注入 @AtEnter/@AtExit/@AtExceptionExit三个地方,
  • @Binding绑定了不同的数据
  • @AtEnter里配置了 inline = true,则说明插入的SampleInterceptor#atEnter函数本身会被inline掉
  • 配置了 suppress = RuntimeException.classsuppressHandler = PrintExceptionSuppressHandler.class,说明插入的代码会被 try/catch 包围
publicstaticclassSampleInterceptor {
@AtEnter(inline = true, suppress = RuntimeException.class, suppressHandler = PrintExceptionSuppressHandler.class)
publicstaticvoidatEnter(@Binding.ThisObjectobject, @Binding.ClassObjectclazz,
@Binding.ArgsObject[] args, @Binding.MethodNameStringmethodName,
@Binding.MethodDescStringmethodDesc) {
System.out.println("atEnter, args[0]: " + args[0]);
}
@AtExit(inline = true)
publicstaticvoidatExit(@Binding.ReturnObjectreturnObject) {
System.out.println("atExit, returnObject: " + returnObject);
}
@AtExceptionExit(inline = true, onException = RuntimeException.class)
publicstaticvoidatExceptionExit(@Binding.ThrowableRuntimeExceptionex,
@Binding.Field(name = "exceptionCount") intexceptionCount) {
System.out.println("atExceptionExit, ex: " + ex.getMessage() + ", field exceptionCount: " + exceptionCount);
}
}

2. @ExceptionHandler

在上面的 @AtEnter配置里,生成的代码会被 try/catch 包围,那么具体的内容是在PrintExceptionSuppressHandler

publicstaticclassPrintExceptionSuppressHandler {
@ExceptionHandler(inline = true)
publicstaticvoidonSuppress(@Binding.ThrowableThrowablee, @Binding.ClassObjectclazz) {
System.out.println("exception handler: " + clazz);
e.printStackTrace();
}
}

3. 查看反编译结果

原始的Sample类是:

publicstaticclassSample {
privateintexceptionCount = 0;
publicStringhello(Stringstr, booleanexception) {
if (exception) {
exceptionCount++;
thrownewRuntimeException("test exception, str: " + str);
}
return"hello " + str;
}
}

增强后的字节码,再反编译:

packagecom.example;
publicstaticclassByteKitDemo.Sample {
privateintexceptionCount = 0;
publicStringhello(Stringstring, booleanbl) {
try {
Stringstring2 = "(Ljava/lang/String;Z)Ljava/lang/String;";
Stringstring3 = "hello";
Object[] arrobject = newObject[]{string, newBoolean(bl)};
Class<ByteKitDemo.Sample> class_ = ByteKitDemo.Sample.class;
ByteKitDemo.Samplesample = this;
System.out.println("atEnter, args[0]: " + arrobject[0]);
}
catch (RuntimeExceptionruntimeException) {
Class<ByteKitDemo.Sample> class_ = ByteKitDemo.Sample.class;
RuntimeExceptionruntimeException2 = runtimeException;
System.out.println("exception handler: " + class_);
runtimeException2.printStackTrace();
}
try {
Stringstring4;
voidstr;
voidexception;
if (exception != false) {
++this.exceptionCount;
thrownewRuntimeException("test exception, str: " + (String)str);
}
Stringstring5 = string4 = "hello " + (String)str;
System.out.println("atExit, returnObject: " + string5);
returnstring4;
}
catch (RuntimeExceptionruntimeException) {
intn = this.exceptionCount;
RuntimeExceptionruntimeException3 = runtimeException;
System.out.println("atExceptionExit, ex: " + runtimeException3.getMessage() + ", field exceptionCount: " + n);
throwruntimeException;
}
}
}

开发相关

deploy到远程仓库:

mvn clean deploy -DskipTests -P release

License

Apache License V2

About

Java Bytecode Kit

Resources

Stars

524 stars

Watchers

12 watching

Forks

Releases

Packages

Used by

Contributors

Languages