目标
- Arthas之前的字节码增强,是通过asm处理的,代码逻辑不好,理解困难
- 基于ASM提供更高层的字节码处理能力,智能诊断/APM领域,不是通用的字节码库
- ByteKit 期望能提供简单的 API,让开发人员可以比较轻松的完成字节码增长
对比
功能 | 函数进出注入点 | 绑定数据 | 排队 | 防止重复增强 | 避免装箱/拆箱 | 原点调用替换 | @ExceptionHandler |
字节套件 | @AtEnter @AtExit @AtExceptionExit @AtFieldAccess @AtInvoke @AtInvokeException @AtLine @AtSyncEnter @AtSyncExit @AtThrow |
this/args/return/throw field locals 子调用入参/返回值/子调用异常 行数 |
✓ | ✓ | ✓ | ✓ | ✓ |
字节好友 | OnMethodEnter @OnMethodExit @OnMethodExit#onThrowable() |
this/args/return/throw 字段 本地人 |
✓ | ✗ | ✓ | ✓ | ✓ |
传统AOP | Enter Exit Exception |
这个/参数/返回/抛出 | ✗ | ✗ | ✗ | ✗ | ✗ |
特性
1. 集中的注入点支持
@AtEnter
函数入口@AtExit
函数退出@AtExceptionExit
函数外异常@AtFieldAccess
访问领域@AtInvoke
在方法里的子函数调用@AtInvokeException
在方法里的子函数调用抛出异常@AtLine
在指定行号@AtSyncEnter
进入同步块,例如synchronized
块@AtSyncExit
退出同步块@AtThrow
代码里显式throw
异常点
2.动态的绑定
@Binding.This
这个对象@@Binding.Class
类对象@Binding.Method
函数调用的方法 对象@Binding.MethodName
函数的名字@Binding.MethodDesc
函数的描述@Binding.Return
函数调用的返回值@Binding.Throwable
函数里抛出的异常@Binding.Args
函数调用的入参@Binding.ArgNames
函数调用的入参的名字@Binding.LocalVars
局部变量@Binding.LocalVarNames
局部变量的名字@Binding.Field
field对象属性字段@Binding.InvokeArgs
方法里的子函数调用的入参@Binding.InvokeReturn
方法里的子函数调用的返回值@Binding.InvokeMethodName
方法里的子函数调用的名字@Binding.InvokeMethodOwner
方法里的子函数调用的类名@Binding.InvokeMethodDeclaration
method里的子函数调用的desc@Binding.Line
行号@Binding.Monitor
同步块里监控的对象
3.异常的处理
@ExceptionHandler
在插入的增强代码中,可以用try/catch
模块外接起来
4.内联支持
增强的代码和异常代码都是通过内联内联到原来的类里,达到最理想的处理技术效果。
5.invokeOrigin技术
经常,我们要增强一个类,就可以在函数中不断插入一个静态的方法的功能呢,有什么办法。那么有没有更灵活的方式?
例如有一个 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
举例说明,[查看源文件]。
1. 定义注入点和绑定数据
- 在下面的SampleInterceptor时定义了要注入
@AtEnter
/@AtExit
/@AtExceptionExit
三个地方, - 用
@Binding
绑定了不同的数据 - 在
@AtEnter
里配置了inline = true
,则说明插入的功能SampleInterceptor#atEnter
会被内联掉 - 配置了
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); } }
2.@ExceptionHandler
上面在的@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(); } }
3. 查看反编译结果
原来的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; } } }
开发相关人员
部署到远程仓库:
mvn clean deploy -DskipTests -P release