1.1. 介绍

javassist是一个开源的分析、编辑和创建Java字节码的类库,通过javassist提供的API可以在java程序运行时编辑一个类的字节码信息,改变该类的结构信息。说简单点,就是一个用来处理Java字节码的类库。

除了Javassist,常见的字节码编程工具有ASMbyte-buddy,这两个工具相对来说更加偏向于底层,需要了解关于jvm的指令;使用javassist可以不需要了解jvm指令,只需使用javassist类库提供的API接口就可以实现字节码编程。

1.2. 使用

1.2.1. 常用类

javassist字节码编程常用的类:

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

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

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

  • CtField:类的属性,通过它可以给类创建新的属性,还可以修改已有的属性的类型,访问修饰符等

  • CtConstructor:用于访问类的构造,与CtMethod类的作用类似

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

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

1.2.2. 依赖

<!-- https://mvnrepository.com/artifact/org.javassist/javassist -->
<dependency>
    <groupId>org.javassist</groupId>
    <artifactId>javassist</artifactId>
    <version>3.28.0-GA</version>
</dependency>

1.2.3. 举例

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

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;

public class Main {
    public static void main(String[] args) throws NotFoundException, CannotCompileException, IOException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        //创建classPool类池对象
        ClassPool classPool = ClassPool.getDefault();
        // 通过classPool创建一个新的类Test
        CtClass test = classPool.makeClass("Test");
        // 创建 void main() 方法,(方法的返回值类型你,方法名,方法的参数类型,方法所属的类)
        CtMethod mainMethod = new CtMethod(CtClass.voidType, "main", new CtClass[]{classPool.get(String[].class.getName())}, test);
        // 设置main方法的访问修饰符 public static
        mainMethod.setModifiers(Modifier.PUBLIC + Modifier.STATIC);
        // 设置方法内容
        mainMethod.setBody("System.out.println(\"test\");");
        // 添加方法
        test.addMethod(mainMethod);
        // 写入当前目录,运行后会在当前项目的根目录生成 Test.class 文件
        test.writeFile();
        // 讲test转换为字节码数组输出
        System.out.println(Arrays.toString(test.toBytecode()));

        // 生成Class对象,反射调用main方法
        Class<?> aClass = test.toClass();
        Object o = aClass.newInstance();
        aClass.getDeclaredMethod("main", String[].class).invoke(o, new String[1]);
    }
}

image-20211028150527308

  • 创建构造函数,和刚才的差不多,微改即可
import javassist.*;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;

public class Main {
    public static void main(String[] args) throws NotFoundException, CannotCompileException, IOException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        //创建classPool类池对象
        ClassPool classPool = ClassPool.getDefault();
        // 通过classPool创建一个新的类Test
        CtClass test = classPool.makeClass("Test");
        // 创建构造函数
        CtConstructor constructor = new CtConstructor(null, test);
        // 设置main方法的访问修饰符 public
        constructor.setModifiers(Modifier.PUBLIC);
        // 设置方法内容
        constructor.setBody("System.out.println(\"test\");");
        // 添加方法
        test.addConstructor(constructor);
        // 写入当前目录,运行后会在当前项目的根目录生成 Test.class 文件
        test.writeFile();

        // 生成Class对象,然后生成实例
        Class<?> aClass = test.toClass();
        Object o = aClass.newInstance();
    }
}

1.3. 参考

Copyright © d4m1ts 2022 all right reserved,powered by Gitbook该文章修订时间: 2021-12-25 18:51:59

results matching ""

    No results matching ""