介绍

**javassist**是一个开源的分析、编辑和创建Java字节码的类库,通过javassist提供的API可以在java程序运行时编辑一个类的字节码信息,改变该类的结构信息。说简单点,就是一个用来处理Java字节码的类库。
除了Javassist,常见的字节码编程工具有ASMbyte-buddy,这两个工具相对来说更加偏向于底层,需要了解关于jvm的指令;使用javassist可以不需要了解jvm指令,只需使用javassist类库提供的API接口就可以实现字节码编程。

使用

常用类

javassist字节码编程常用的类:

  • ClassPool:ClassPool 类可以控制的类的字节码,例如创建一个类或加载一个类,与JVM类装载器类似;它是基于哈希表(Hashtable)实现的CtClass对象容器,其中键名是类名称,值是表示该类的CtClass对象(HashtableHashmap类似都是实现map接口,hashmap可以接收null的值,但是Hashtable不行)。

    1. public static synchronized ClassPool getDefault() // 返回默认的类池对象。
    2. public ClassPath insertClassPath(String pathname) // 在搜索路径的开头插入目录或jar(或zip)文件。
    3. public ClassPath insertClassPath(ClassPath cp) // ClassPath在搜索路径的开头插入一个对象。
    4. public ClassLoader getClassLoader() // 获取类加载器toClass(),getAnnotations()在 CtClass等
    5. public CtClass get(String classname) // 从源中读取类文件,并返回对CtClass 表示该类文件的对象的引用。
    6. public ClassPath appendClassPath(ClassPath cp) // 将ClassPath对象附加到搜索路径的末尾。
    7. public CtClass makeClass(String classname) // 创建一个新的public类
  • CtClass: CtClass表示编译时的一个类,它提供了类的操作,如在类中动态添加新字段、方法和构造函数、以及改变类、父类和接口的方法

    1. public void setSuperclass(CtClass clazz) // 更改超类,除非此对象表示接口。
    2. public Class<?> toClass(Lookup lookup) // 将此类转换为java.lang.Class对象。
    3. public byte[] toBytecode() // 将该类转换为字节码数组。
    4. public void writeFile() // 将由此CtClass对象表示的类文件写入当前目录。
    5. public void writeFile(String directoryName) // 将由此CtClass 对象表示的类文件写入本地磁盘。
    6. public CtConstructor makeClassInitializer() // 制作一个空的类初始化程序(静态构造函数)。
  • CtMethod:表示类中的方法,通过它可以给类创建新的方法,还可以修改返回类型,访问修饰符等,甚至还可以修改方法体内容代码

  • CtField:类的属性,通过它可以给类创建新的属性,还可以修改已有的属性的类型,访问修饰符等
  • CtConstructor:用于访问类的构造,与CtMethod类的作用类似

    1. public void setBody(String src) // 设置构造函数主体
    2. public void setBody(CtConstructor src, ClassMap map) // 从另一个构造函数复制一个构造函数主体。
    3. public CtMethod toMethod(String name, CtClass declaring, ClassMap map) // 复制此构造函数并将其转换为方法
  • ClassClassPath:该类作用是用于通过getResourceAsStream()java.lang.Class中获取类文件的搜索路径。

    1. public ClassClassPath(Class<?> c) // 构造函数,创建一个搜索路径
    2. public URL find(String classname) // 获取指定类文件的URL
    3. public InputStream openClassfile(String classname) // 通过getResourceAsStream()获取类

    依赖

    1. <!-- https://mvnrepository.com/artifact/org.javassist/javassist -->
    2. <dependency>
    3. <groupId>org.javassist</groupId>
    4. <artifactId>javassist</artifactId>
    5. <version>3.28.0-GA</version>
    6. </dependency>

    举例

  • 创建对象Test,并创建public static void main( String[] )方法,最后反射调用

    1. import javassist.*;
    2. import java.io.IOException;
    3. import java.lang.reflect.InvocationTargetException;
    4. import java.util.Arrays;
    5. public class Main {
    6. public static void main(String[] args) throws NotFoundException, CannotCompileException, IOException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
    7. //创建classPool类池对象
    8. ClassPool classPool = ClassPool.getDefault();
    9. // 通过classPool创建一个新的类Test
    10. CtClass test = classPool.makeClass("Test");
    11. // 创建 void main() 方法,(方法的返回值类型你,方法名,方法的参数类型,方法所属的类)
    12. CtMethod mainMethod = new CtMethod(CtClass.voidType, "main", new CtClass[]{classPool.get(String[].class.getName())}, test);
    13. // 设置main方法的访问修饰符 public static
    14. mainMethod.setModifiers(Modifier.PUBLIC + Modifier.STATIC);
    15. // 设置方法内容
    16. mainMethod.setBody("System.out.println(\"test\");");
    17. // 添加方法
    18. test.addMethod(mainMethod);
    19. // 写入当前目录,运行后会在当前项目的根目录生成 Test.class 文件
    20. test.writeFile();
    21. // 讲test转换为字节码数组输出
    22. System.out.println(Arrays.toString(test.toBytecode()));
    23. // 生成Class对象,反射调用main方法
    24. Class<?> aClass = test.toClass();
    25. Object o = aClass.newInstance();
    26. aClass.getDeclaredMethod("main", String[].class).invoke(o, new String[1]);
    27. }
    28. }

    08.javassist字节码编程 - 图1

  • 创建构造函数,和刚才的差不多,微改即可

    1. import javassist.*;
    2. import java.io.IOException;
    3. import java.lang.reflect.InvocationTargetException;
    4. public class Main {
    5. public static void main(String[] args) throws NotFoundException, CannotCompileException, IOException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
    6. //创建classPool类池对象
    7. ClassPool classPool = ClassPool.getDefault();
    8. // 通过classPool创建一个新的类Test
    9. CtClass test = classPool.makeClass("Test");
    10. // 创建构造函数
    11. CtConstructor constructor = new CtConstructor(null, test);
    12. // 设置main方法的访问修饰符 public
    13. constructor.setModifiers(Modifier.PUBLIC);
    14. // 设置方法内容
    15. constructor.setBody("System.out.println(\"test\");");
    16. // 添加方法
    17. test.addConstructor(constructor);
    18. // 写入当前目录,运行后会在当前项目的根目录生成 Test.class 文件
    19. test.writeFile();
    20. // 生成Class对象,然后生成实例
    21. Class<?> aClass = test.toClass();
    22. Object o = aClass.newInstance();
    23. }
    24. }

    参考

  • 10-java安全基础——javassist字节码编程

  • Java安全之Javassist动态编程