Java字节码工具包 ByteKit

简介: ByteKit 期望能提供简单的 API,让开发人员可以比较轻松的完成字节码增长

目标

  1. Arthas之前的字节码增强,是通过asm处理的,代码逻辑不好,理解困难
  2. 基于ASM提供更高层的字节码处理能力,智能诊断/APM领域,不是通用的字节码库
  3. 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.classsuppressHandler = 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


相关文章
|
13天前
|
Java
轻松上手Java字节码编辑:IDEA插件VisualClassBytes全方位解析
本插件VisualClassBytes可修改class字节码,包括class信息、字段信息、内部类,常量池和方法等。
64 6
|
2月前
|
安全 Java API
JAVA并发编程JUC包之CAS原理
在JDK 1.5之后,Java API引入了`java.util.concurrent`包(简称JUC包),提供了多种并发工具类,如原子类`AtomicXX`、线程池`Executors`、信号量`Semaphore`、阻塞队列等。这些工具类简化了并发编程的复杂度。原子类`Atomic`尤其重要,它提供了线程安全的变量更新方法,支持整型、长整型、布尔型、数组及对象属性的原子修改。结合`volatile`关键字,可以实现多线程环境下共享变量的安全修改。
|
3月前
|
安全 Java 调度
解锁Java并发编程高阶技能:深入剖析无锁CAS机制、揭秘魔法类Unsafe、精通原子包Atomic,打造高效并发应用
【8月更文挑战第4天】在Java并发编程中,无锁编程以高性能和低延迟应对高并发挑战。核心在于无锁CAS(Compare-And-Swap)机制,它基于硬件支持,确保原子性更新;Unsafe类提供底层内存操作,实现CAS;原子包java.util.concurrent.atomic封装了CAS操作,简化并发编程。通过`AtomicInteger`示例,展现了线程安全的自增操作,突显了这些技术在构建高效并发程序中的关键作用。
70 1
|
25天前
|
Java 数据格式 索引
使用 Java 字节码工具检查类文件完整性的原理是什么
Java字节码工具通过解析和分析类文件的字节码,检查其结构和内容是否符合Java虚拟机规范,确保类文件的完整性和合法性,防止恶意代码或损坏的类文件影响程序运行。
|
25天前
|
Java API Maven
如何使用 Java 字节码工具检查类文件的完整性
本文介绍如何利用Java字节码工具来检测类文件的完整性和有效性,确保类文件未被篡改或损坏,适用于开发和维护阶段的代码质量控制。
|
1月前
|
Java Apache Maven
Java/Spring项目的包开头为什么是com?
本文介绍了 Maven 项目的初始结构,并详细解释了 Java 包命名惯例中的域名反转规则。通过域名反转(如 `com.example`),可以确保包名的唯一性,避免命名冲突,提高代码的可读性和逻辑分层。文章还讨论了域名反转的好处,包括避免命名冲突、全球唯一性、提高代码可读性和逻辑分层。最后,作者提出了一个关于包名的问题,引发读者思考。
Java/Spring项目的包开头为什么是com?
|
1月前
|
Java
如何从Java字节码角度分析问题|8月更文挑战
如何从Java字节码角度分析问题|8月更文挑战
|
2月前
|
Java API 数据处理
Java 包(package)的作用详解
在 Java 中,包(package)用于组织和管理类与接口,具有多项关键作用:1)系统化组织代码,便于理解和维护;2)提供命名空间,避免类名冲突;3)支持访问控制,如 public、protected、默认和 private,增强封装性;4)提升代码可维护性,实现模块化开发;5)简化导入机制,使代码更简洁;6)促进模块化编程,提高代码重用率;7)管理第三方库,避免命名冲突;8)支持 API 设计,便于功能调用;9)配合自动化构建工具,优化项目管理;10)促进团队协作,明确模块归属。合理运用包能显著提升代码质量和开发效率。
|
2月前
|
Java 数据安全/隐私保护
Java 包(package)的使用详解
Java中的包(`package`)用于组织类和接口,避免类名冲突并控制访问权限,提升代码的可维护性和可重用性。通过`package`关键字定义包,创建相应目录结构即可实现。包可通过`import`语句导入,支持导入具体类或整个包。Java提供多种访问权限修饰符(`public`、`protected`、`default`、`private`),以及丰富的标准库包(如`java.lang`、`java.util`等)。合理的包命名和使用对大型项目的开发至关重要。
|
2月前
|
Arthas Java 测试技术
Java字节码文件、组成,jclasslib插件、阿里arthas工具,Java注解
Java字节码文件、组成、详解、分析;常用工具,jclasslib插件、阿里arthas工具;如何定位线上问题;Java注解
Java字节码文件、组成,jclasslib插件、阿里arthas工具,Java注解