天天看点

java字节码生成框架及使用示例1 字节码生成框架2. 使用代码示例

目前Java字节码生成框架大致有ASM、Javassist和byte buddy

BECL不常用,ByteKit是Arthas中使用的字节码工具集

1 字节码生成框架

1.1 ASM

1.1.1 介绍

ASM官网 介绍如下:

ASM是一个通用的Java字节码操作和分析框架。它能够以二进制形式修改已有的类或是动态生成类。

ASM提供了一些常见的字节码转换和分析算法,可以根据这些算法构建定制的复杂转换和代码分析工具。

ASM提供了与其他Java字节码框架类似的功能,但侧重于性能。因为它的设计和实现是尽可能的小和快,所以它非常适合在动态系统中使用(当然也可以在静态方式中使用,例如在编译器中)

源码 ,当前最新版本是9.2

1.1.2 典型案例

ASM在许多项目中使用,如实现动态代理的CGLIB,包括但不限于如下:

  • the OpenJDK, to generate the lambda call sites, and also in the Nashorn compiler,
  • the Groovy compiler and the Kotlin compiler,
  • Cobertura and Jacoco, to instrument classes in order to measure code coverage,
  • CGLIB, to dynamically generate proxy classes (which are used in other projects such as Mockito and EasyMock),
  • Gradle, to generate some classes at runtime.
  • Arthas(阿尔萨斯)Arthas增强功能的核心是Enhancer和AdviceWeaver这两个类,对方法进行Aop织入,达到watch,trace等效果。

1.2 Javassist

javassist是jboss的一个子项目,其主要的优点,在于简单,而且快速。

源码, 当前最新版本是:3.28.0-GA

Javassist (Java编程助手)使Java字节码操作变得简单。它是一个用于在Java中编辑字节码的类库; 它使Java程序能够在运行时定义一个新类,并在JVM加载类文件时修改类文件。

与其他类似的字节码编辑器不同,Javassist提供了两个级别的API:

  • 源代码级别: 可以编辑类文件,而不需要了解Java字节码的规范。整个API直接使用java编码的形式进行设计。甚至可以以源文本【source text】的形式指定插入的字节码;Javassist动态地编译它。
  • 字节码级别:字节码级API允许用户像其他编辑器一样直接编辑类文件。

1.3 bytebuddy

1.3.1 介绍

Byte Buddy是一个代码生成和操作库,用于在Java应用程序运行时创建和修改Java类,无需编译器的帮助。除了Java类库附带的代码生成实用程序外,Byte Buddy允许创建任意类,而且不限于实现创建运行时代理的接口。此外,Byte Buddy提供了一个方便的API,可以使用Java代理或在构建过程中手动更改类。

要使用Byte Buddy,不需要理解Java字节代码或类文件格式。相比之下,Byte Buddy的API旨在编写简洁且易于理解的代码。

API被设计成尽可能非侵入性,因此,Byte Buddy不会在它创建的类中留下任何痕迹。因此,生成的类可以存在,而不需要类路径上的Byte Buddy。因为这个特性,Byte Buddy的吉祥物被选为幽灵,如下图。

java字节码生成框架及使用示例1 字节码生成框架2. 使用代码示例

Byte Buddy是用Java 5编写的,但支持为任何Java版本生成类。Byte Buddy是一个轻量级的库,只依赖于Java字节代码解析器库ASM的访问者API, ASM本身不需要任何进一步的依赖。

1.3.2 典型案例

bytebuddy在许多项目中使用,包括但不限于如下:

  • Apache SkyWalking: 分布式系统的应用程序性能监视工具,用来增强agent的监控点的类,实现性能监控指标。

1.4 BCEL

Commons BCEL

字节代码工程库(Apache Commons BCEL™)旨在为用户提供一种方便的方法来分析、创建和操作(二进制)Java类文件(以.class结尾的文件)。类由对象表示,对象包含给定类的所有符号信息:特别是方法、字段和字节代码指令。

这些对象可以从现有文件中读取,由程序转换(例如,运行时的类加载器),然后再次写入文件。一个更有趣的应用程序是在运行时从头创建类。如果您想了解Java虚拟机(JVM)和Java .class文件的格式,字节代码工程库(BCEL)可能也很有用。

BCEL包含一个名为JustIce的字节代码验证器,它通常提供比标准JVM消息更好的关于代码错误的信息。

当前的版本是:

<dependency>
    <groupId>org.apache.bcel</groupId>
    <artifactId>bcel</artifactId>
    <version>6.5.0</version>
</dependency>
           

1.5 ByteKit

ByteKit:bytecode kit for java,在Arthas中使用的字节码工具集,目标是:

  • 之前的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

Enter

Exit

Exception

this/args/return/throw

2. 使用代码示例

2.1 asm示例

实现后的效果仅仅是记录方法的执行时间:

public static void show() {
        long l = System.currentTimeMillis();
        System.out.println("Hello World");
        long l2 = System.currentTimeMillis() - l;
        System.out.println("\u65b9\u6cd5\u6267\u884c\u8017\u65f6\u662f:" + l2 + " ms");
    }
           

执行结果是:

Hello World

方法执行耗时是:0 ms

源码示例

import com.alibaba.bytekit.utils.AgentUtils;
import com.alibaba.bytekit.utils.Decompiler;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;

import java.io.IOException;
import java.lang.instrument.UnmodifiableClassException;

class ASMDemo {
    public static void main(String[] args) throws IOException, UnmodifiableClassException {
        ClassReader cr = new ClassReader(User.class.getName());
        ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS);
        ClassVisitor cv = new MyClassVisitor(cw);
        cr.accept(cv, Opcodes.ASM5);
        // 获取生成的class文件对应的二进制流
        byte[] bytes = cw.toByteArray();

        //将二进制流写到out/下
//        FileOutputStream fos = new FileOutputStream("F:/tmp/User.class");
//        fos.write(code);
//        fos.close();

        //查看增强后反编译的结果
        System.out.println(Decompiler.decompile(bytes));

        // 通过 reTransform 增强类
        AgentUtils.reTransform(User.class, bytes);
        User.show();
    }
}

class User {
    public static void show(){
        System.out.println("Hello World");
    }
}

import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

public class MyClassVisitor extends ClassVisitor {
    public MyClassVisitor(final ClassVisitor cv) {
        super(Opcodes.ASM9, cv);
    }

    @Override
    public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
        if (cv != null) {
            cv.visit(version, access, name, signature, superName, interfaces);
        }
    }

    @Override
    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
        //如果methodName是show,则返回自定义的MyMethodVisitor 
        if ("show".equals(name)) {
            MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
            return new MyMethodVisitor(mv);
        }
        if (cv != null) {
            return cv.visitMethod(access, name, desc, signature, exceptions);
        }
        return null;
    }
}

import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

public class MyMethodVisitor extends MethodVisitor implements Opcodes {
    public MyMethodVisitor(MethodVisitor mv) {
        super(Opcodes.ASM9, mv);
    }

    @Override
    public void visitCode() {
        //方法体内开始时调用
        mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
        mv.visitVarInsn(LSTORE, 0);
        super.visitCode();
    }

    @Override
    public void visitInsn(int opcode) {
        //每执行一个指令都会调用
        if (opcode == Opcodes.RETURN) {
            mv.visitMethodInsn(INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J", false);
            mv.visitVarInsn(LLOAD, 0);
            mv.visitInsn(LSUB);
            mv.visitVarInsn(LSTORE, 2);
            Label l3 = new Label();
            mv.visitLabel(l3);
            mv.visitLineNumber(11, l3);
            mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv.visitTypeInsn(NEW, "java/lang/StringBuilder");
            mv.visitInsn(DUP);
            mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
            mv.visitLdcInsn("== method cost time = ");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
            mv.visitVarInsn(LLOAD, 2);
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(J)Ljava/lang/StringBuilder;", false);
            mv.visitLdcInsn(" ==");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);

        }
        super.visitInsn(opcode);
    }
}
           

maven依赖

<asm.version>9.0</asm.version>

<dependency>
			<groupId>org.ow2.asm</groupId>
			<artifactId>asm</artifactId>
			<version>${asm.version}</version>
		</dependency>
		<dependency>
			<groupId>org.ow2.asm</groupId>
			<artifactId>asm-commons</artifactId>
			<version>${asm.version}</version>
		</dependency>
		<dependency>
			<groupId>org.ow2.asm</groupId>
			<artifactId>asm-util</artifactId>
			<version>${asm.version}</version>
		</dependency>
		<dependency>
			<groupId>org.ow2.asm</groupId>
			<artifactId>asm-analysis</artifactId>
			<version>${asm.version}</version>
		</dependency>
           

2.2 bytebuddy示例

maven依赖

<dependency>
			<groupId>net.bytebuddy</groupId>
			<artifactId>byte-buddy</artifactId>
			<version>1.10.18</version>
		</dependency>
           

源码示例

class ByteBuddyTest {

    public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException {
        Class dynamicType = new ByteBuddy()
                .subclass(Dog.class)
                .method(ElementMatchers.named("say"))
                .intercept(MethodDelegation.to(MyServiceInterceptor.class))
                .make()
                .load(ByteBuddyTest.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
                .getLoaded();

        Object s = dynamicType.newInstance();
        System.out.println(((Dog)s).say(" 欢迎测试bytebuddy"));
    }
}

public class MyServiceInterceptor {
    @RuntimeType
    public static String intercept(
            //用于调用父类版本的方法
            @SuperCall Callable<String> zuper,
            @AllArguments Object[] allArguments,
            //被拦截的原始方法
            @Origin Method method
    ) throws Exception {
        System.out.println("intercept:拦截" + method.getName());
        StringBuffer sb = new StringBuffer();
        Arrays.stream(allArguments).forEach(arg -> sb.append(arg).append(","));
        System.out.println("参数是:" + sb.toString());
        String res = zuper.call();
        System.out.println("执行结果是:" + res);
        return res;
    }
}

public class Dog {
    public String say(String msg){
        return "汪汪汪"+msg;
    }
}
           

2.3 Bytekit示例

maven依赖

<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>bytekit-core</artifactId>
			<version>0.0.4</version>
		</dependency>
		<dependency>
			<groupId>net.bytebuddy</groupId>
			<artifactId>byte-buddy-agent</artifactId>
			<version>1.10.18</version>
		</dependency>
		<dependency>
			<groupId>org.benf</groupId>
			<artifactId>cfr</artifactId>
			<version>0.150</version>
		</dependency>
           

源码示例

import java.util.List;
import java.util.concurrent.TimeUnit;

import com.alibaba.deps.org.objectweb.asm.tree.ClassNode;
import com.alibaba.deps.org.objectweb.asm.tree.MethodNode;
import com.alibaba.bytekit.asm.MethodProcessor;
import com.alibaba.bytekit.asm.binding.Binding;
import com.alibaba.bytekit.asm.interceptor.InterceptorProcessor;
import com.alibaba.bytekit.asm.interceptor.annotation.AtEnter;
import com.alibaba.bytekit.asm.interceptor.annotation.AtExceptionExit;
import com.alibaba.bytekit.asm.interceptor.annotation.AtExit;
import com.alibaba.bytekit.asm.interceptor.annotation.ExceptionHandler;
import com.alibaba.bytekit.asm.interceptor.parser.DefaultInterceptorClassParser;
import com.alibaba.bytekit.utils.AgentUtils;
import com.alibaba.bytekit.utils.AsmUtils;
import com.alibaba.bytekit.utils.Decompiler;

public class ByteKitDemo {

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

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

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

    public static void main(String[] args) throws Exception {
        AgentUtils.install();

        // 解析定义的 Interceptor类 和相关的注解
        DefaultInterceptorClassParser interceptorClassParser = new DefaultInterceptorClassParser();
        List<InterceptorProcessor> processors = interceptorClassParser.parse(SampleInterceptor.class);

        // 加载字节码
        ClassNode classNode = AsmUtils.loadClass(Sample.class);

        // 对加载到的字节码做增强处理
        for (MethodNode methodNode : classNode.methods) {
            if (methodNode.name.equals("hello")) {
                MethodProcessor methodProcessor = new MethodProcessor(classNode, methodNode);
                for (InterceptorProcessor interceptor : processors) {
                    interceptor.process(methodProcessor);
                }
            }
        }

        // 获取增强后的字节码
        byte[] bytes = AsmUtils.toBytes(classNode);

        // 查看反编译结果
        System.out.println(Decompiler.decompile(bytes));

        // 通过 reTransform 增强类
        AgentUtils.reTransform(Sample.class, bytes);

        // 启动Sample,不断执行
        final Sample sample = new Sample();
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100; ++i) {
                    try {
                        TimeUnit.SECONDS.sleep(3);
                        String result = sample.hello("" + i, (i % 3) == 0);
                        System.out.println("call hello result: " + result);
                    } catch (Throwable e) {
                        // ignore
                        System.out.println("call hello exception: " + e.getMessage());
                    }
                }
            }
        });
        t.start();
        System.in.read();
    }
}