Home

Awesome

ByteKit

JavaCI maven license Average time to resolve an issue Percentage of issues still open

bytecode kit for java.

目标

  1. 之前的Arthas里的字节码增强,是通过asm来处理的,代码逻辑不好修改,理解困难
  2. 基于ASM提供更高层的字节码处理能力,面向诊断/APM领域,不是通用的字节码库
  3. ByteKit期望能提供一套简洁的API,让开发人员可以比较轻松的完成字节码增强

对比

功能函数Enter/Exit注入点绑定数据inline防止重复增强避免装箱/拆箱开销origin调用替换@ExceptionHandler
ByteKit@AtEnter <br> @AtExit <br>@AtExceptionExit <br> @AtFieldAccess <br> @AtInvoke<br>@AtInvokeException<br>@AtLine<br>@AtSyncEnter<br>@AtSyncExit<br>@AtThrowthis/args/return/throw<br>field<br>locals<br>子调用入参/返回值/子调用异常<br>line number
ByteBuddy@OnMethodEnter<br>@OnMethodExit<br> @OnMethodExit#onThrowable()this/args/return/throw<br>field<br>locals
传统AOPEnter<br>Exit<br>Exceptionthis/args/return/throw

特性

1. 丰富的注入点支持

2. 动态的Binding

3. 可编程的异常处理

4. inline支持

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

5. invokeOrigin 技术

通常,我们要增强一个类,就想要办法在函数前后插入一个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为例说明,[查看源文件]。

1. 定义注入点和Binding数据

    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;
        }
    }
}

开发相关

deploy到远程仓库:

mvn clean deploy -DskipTests -P release

License

Apache License V2