介绍
javassist
是一个开源的分析、编辑和创建Java字节码的类库,通过javassist提供的API可以在java程序运行时编辑一个类的字节码信息,改变该类的结构信息。说简单点,就是一个用来处理Java字节码的类库。
除了Javassist,常见的字节码编程工具有ASM
和byte-buddy
,这两个工具相对来说更加偏向于底层,需要了解关于jvm的指令;使用javassist
可以不需要了解jvm指令,只需使用javassist类库提供的API接口就可以实现字节码编程。
使用
常用类
javassist字节码编程常用的类:
ClassPool
:ClassPool 类可以控制的类的字节码,例如创建一个类或加载一个类,与JVM类装载器类似;它是基于哈希表(Hashtable
)实现的CtClass
对象容器,其中键名是类名称,值是表示该类的CtClass
对象(Hashtable
和Hashmap
类似都是实现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()获取类
依赖
<!-- https://mvnrepository.com/artifact/org.javassist/javassist --> <dependency> <groupId>org.javassist</groupId> <artifactId>javassist</artifactId> <version>3.28.0-GA</version> </dependency>
举例
创建对象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]); } }
创建构造函数,和刚才的差不多,微改即可
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(); } }
参考
- Java安全之Javassist动态编程