Java字节码操作全解析:Javassist篇

发表时间: 2023-12-30 12:18

javassist全称(Java Programming Assistant),是一款比JDK内置库操作字节码更方便的代码库。为了方便实验,先引入它的依赖

<dependency>    <groupId>org.javassist</groupId>    <artifactId>javassist</artifactId>    <version>3.21.0-GA</version></dependency>

什么是字节码呢?字节码就是一套可以被JVM处理的指令集,JVM将字节码指令转换为机器级汇编指令。举例如下:

创建一个User类,并用javac编译后得到User.class

public class User {    private String name;    private int age;    public void baseinfo(String name, int age){        this.name = name;        this.age = age;    }}

然后 javap -c User.class 输出下面的字节码(java version "1.8.0_181")

public class bluesky.javassist.User {  public bluesky.javassist.User();    Code:       0: aload_0       1: invokespecial #1                  // Method java/lang/Object."<init>":()V       4: return  public void baseinfo(java.lang.String, int);    Code:       0: aload_0       1: aload_1       2: putfield      #2                  // Field name:Ljava/lang/String;       5: aload_0       6: iload_2       7: putfield      #3                  // Field age:I      10: return}

aload_0指令将this指针入栈,aload_1将name变量入栈,putfield是实例字段访问指令,此处就是设置name字段。javassist可以帮助我们非常方便的操作这些字节码。

使用javassist生成一个类:

// 创建一个ClassFile对象ClassFile cf = new ClassFile(false, "bluesky.javassist.JavassistGeneratedClass", null);// 设置JavassistGeneratedClass实现Cloneable接口cf.setInterfaces(new String[] {"java.lang.Cloneable"});// 添加id属性FieldInfo f = new FieldInfo(cf.getConstPool(), "id", "I");// 属性的修饰符f.setAccessFlags(AccessFlag.PUBLIC);cf.addField(f);ClassPool classPool = ClassPool.getDefault();// 生成JavassistGeneratedClass类并获取它的所有属性Field[] fields = classPool.makeClass(cf).toClass().getFields(); // 断言是否存在id属性assertEquals(fields[0].getName(), "id");

获取方法baseinfo的指令:

ClassPool cp = ClassPool.getDefault();ClassFile cf = cp.get("bluesky.javassist.User")  .getClassFile();MethodInfo minfo = cf.getMethod("baseinfo");CodeAttribute ca = minfo.getCodeAttribute();CodeIterator ci = ca.iterator();List<String> operations = new LinkedList<>();while (ci.hasNext()) {    int index = ci.next();    int op = ci.byteAt(index);    operations.add(Mnemonic.OPCODE[op]);}assertEquals(operations,  Arrays.asList(  "aload_0",   "aload_1",   "putfield",   "aload_0",   "iload_2",    "putfield",   "return"));

添加构造方法:

ClassFile cf = ClassPool.getDefault().get("bluesky.javassist.User").getClassFile();Bytecode code = new Bytecode(cf.getConstPool());code.addAload(0);code.addInvokespecial("java/lang/Object", MethodInfo.nameInit, "()V");code.addReturn(null);MethodInfo minfo = new MethodInfo(cf.getConstPool(), MethodInfo.nameInit, "()V");minfo.setCodeAttribute(code.toCodeAttribute());cf.addMethod(minfo);

在方法指定为止插入自己需要的逻辑:

        ClassPool classPool = ClassPool.getDefault();        CtClass ctClass = classPool.get("bluesky.javassist.User");        CtMethod declaredMethod = ctClass.getDeclaredMethod("baseinfo");        declaredMethod.insertBefore("System.out.println(\"方法执行的第一行\")");        declaredMethod.insertAfter("System.out.println(\"方法return前\")");        User instance = (User) ctClass.toClass().newInstance();        instance.baseinfo("hello", 1);

通常可以使用字节码技术实现动态代理、热加载、debug、结合agent实现应用指标监控等。