什么是Javassist


Javassist(Java 编程助手)使 Java 字节码操作变得简单。它是Java中用于编辑字节码的类库;它使 Java 程序能够在运行时定义一个新类,并在 JVM 加载类文件时修改它。与其他类似的字节码编辑器不同,Javassist 提供了两个级别的 API:源代码级和字节码级。如果用户使用源级 API,他们可以在不了解 Java 字节码规范的情况下编辑类文件。整个 API 仅使用 Java 语言的词汇表设计。您甚至可以以源文本的形式指定插入的字节码;Javassist 即时编译它。另一方面,字节码级别的 API 允许用户像其他编辑器一样直接编辑类文件。

如何使用

使用Javassist生成新的Java类

下面用一个例子展示Javassist的用法:

本例子使用IDEA做开发工具,新建Java项目,并在项目根目录下创建了lib目录,在lib目录下拷贝了javassist.jar文件,并右键->Add as Library导入该jar包。

在IDEA中新建一个类com.test.javassist.Test,并添加如下代码:

package com.test.javassist;

import javassist.*;

import java.lang.reflect.Method;

public class Test {

    public static void main(String[] args) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        // 创建一个Person类
        CtClass clz = pool.makeClass("com.test.javassist.Person");
        // 创建一个字符串类型的成员变量,变量名称为name
        CtField field = new CtField(pool.get("java.lang.String"), "name", clz);
        // 设置name成员变量为私有属性
        field.setModifiers(Modifier.PRIVATE);
        // 将name成员变量添加到Person类中
        clz.addField(field);
        // 为Person类提供成员变量name的setter和getter方法
        clz.addMethod(CtNewMethod.setter("setName", field));
        clz.addMethod(CtNewMethod.getter("getName", field));
        // 为Person类添加无参数构造方法
        CtConstructor defaultConstructor = new CtConstructor(new CtClass[]{}, clz);
        defaultConstructor.setBody("{name = \"\";}");
        clz.addConstructor(defaultConstructor);
        // 为Person类添加有参数的构造方法
        CtConstructor paramsConstructor = new CtConstructor(new CtClass[]{pool.get("java.lang.String")}, clz);
        // $0表示this,$1,$2...表示方法参数
        paramsConstructor.setBody("{$0.name = $1;}");
        clz.addConstructor(paramsConstructor);
        // 为Person类创建一个sayHello方法
        CtMethod sayHello = new CtMethod(CtClass.voidType, "sayHello", new CtClass[]{}, clz);
        sayHello.setModifiers(Modifier.PUBLIC);
        sayHello.setBody("System.out.println(\"hello, this is \" + $0.name);");
        clz.addMethod(sayHello);
        // 将Person类写入文件,没有参数则写入到当前项目的根目录
        clz.writeFile();
    }

}

运行上面的代码后,在当前目录下会生成一个Person.class文件,使用IDEA打开可以看到如下源码:

javassist 在哪个jar包 javassist android_Android


下面用反射来验证以上代码生成的Person类的正确性:

// 使用反射测试Person类
Class<?> aClass = Class.forName("com.test.javassist.Person");
Object personObj = aClass.newInstance();
Method setNameMethod = personObj.getClass().getDeclaredMethod("setName", String.class);
setNameMethod.invoke(personObj,"zhangsan");
personObj.getClass().getDeclaredMethod("sayHello").invoke(personObj);

执行上面这段代码,可以看到控制台输出如下:

javassist 在哪个jar包 javassist android_javassist 在哪个jar包_02


这表示前面使用Javassist生成的Person类正确无误。

使用Javassist修改已有的Java类

上面我们使用Javassist动态创建了Person类,如果要修改一个已有的Java类,可以使用如下方法,下面还是以上面的Person类为例,在sayHello方法中打印一行日志,代码实现如下:

ClassPool pool = ClassPool.getDefault();
// 拿到Person类
CtClass ctClass = pool.getCtClass("com.test.javassist.Person");
// 获取Person类的sayHello方法
CtMethod sayHello = ctClass.getDeclaredMethod("sayHello");
// 在方法开始处插入一行代码
sayHello.insertBefore("System.out.println(\"this log is inserted before call sayHello()\");");
// 修改完成后写入文件,方便查看是否修改成功
ctClass.writeFile();

执行上面的代码后,我们在项目根目录下可以发现已经自动生成了新的com.test.javassist.Person.class文件,反编译后源码如下:

javassist 在哪个jar包 javassist android_javassist_03


可以看到Javassist成功修改了Person类的字节码。

Javassist在Android中的用途

由于Javassist可以直接操作字节码,故在Android中可以用于全埋点(无感知的埋点)、热修复等。后续博文会详细记录Javassist在Android中的应用。