在Java中其字节码以二进制的形式存储在class文件中,每一个class文件都包含一个java类或接口,我们可以通过一些动态字节码技术去实现动态创建类、添加类的属性和方法、设置类的父类,以及修改类的方法等操作。常用的动态字节码类库有Javassist、ASM等。而Javassist相比ASM其不需要接触JVM底层的指令,只需要使用Javassist提供API接口就可以实现动态字节码编程,Mybatis动态是实现Dao接口底层也是使用到了Javassist技术,所以这篇文章主要介绍一下Javassist的使用。
Javassist依赖导入
使用Javassist之前需要导入依赖,在Maven的pom添加如下依赖
<dependency> <groupId>org.javassist</groupId> <artifactId>javassist</artifactId> <version>3.29.1-GA</version> </dependency>
Javassist常用类
ClassPool:ClassPool 类可以控制的类的字节码,例如创建一个类或加载一个类,与 JVM 类装载器类似 CtClass: CtClass提供了类的操作,如在类中动态添加新字段、方法和构造函数、以及改变类、父类和接口的方法 CtField:类的属性,通过它可以给类创建新的属性,还可以修改已有的属性的类型,访问修饰符等 CtMethod:表示类中的方法,通过它可以给类创建新的方法,还可以修改返回类型,访问修饰符等, 甚至还可以修改方法体内容代码 CtConstructor:用于访问类的构造,与CtMethod类的作用类似
Javassist使用
添加方法
看看如下一段代码
package ysoserial; import javassist.ClassPool; import javassist.CtClass; import javassist.CtMethod; import java.lang.reflect.Method; import java.lang.reflect.Modifier; public class Test { public static void main(String[] args) throws Exception { //获取类池 ClassPool pool = ClassPool.getDefault(); // 利用类池创建类 CtClass ctClass = pool.makeClass("ysoserial.JavassistTest"); // 创建方法 // 1.返回值类型 2.方法名 3.形式参数列表 4.所属类 CtMethod ctMethod = new CtMethod(CtClass.voidType, "execute", new CtClass[]{}, ctClass); // 设置方法的修饰符列表 ctMethod.setModifiers(Modifier.PUBLIC); // 设置方法体 ctMethod.setBody("{System.out.println(\"hello world\");}"); // 给类添加方法 ctClass.addMethod(ctMethod); // 调用方法 Class<?> aClass = ctClass.toClass(); Object o = aClass.newInstance(); Method method = aClass.getDeclaredMethod("execute"); method.invoke(o); } }
首先在代码的12行-14创建了一个类池,然后利用makeClass去创建一个类
当一个类已存在的时候,我们还可以利用getCtClass去获取这个类,然后对这个类进行动态修改
CtClass ctClass1 = classPool.getCtClass("ysoserial.JavassistTest");
在创建或获取一个类之后,我们通过会把这个对象赋值给一个ctClass句柄,然后通过ctClass句柄去操作这个类
代码的第17行利用CtMethod类添加一个execute方法,在new CtMethod里面依次有四个参数,分别为
CtClass.voidType //返回值的类型 "execute" //添加的方法名 new CtClass[]{} //形参列表 ctClass //所属类,也就是我们利用类池创建出来的那个类
返回的参数类型,可以在CtClass这类的源码里面可以清晰查看
代码的第19行设置我们之前定义的execute这个方法的访问修饰符
ctMethod.setModifiers(Modifier.PUBLIC);
代码的第19行设置我们之前定义的execute这个方法的访问修饰符
ctMethod.setBody("{System.out.println(\"hello world\");}");
代码的第21行设置我们之前定义的execute这个方法体里面的内容
ctMethod.setBody("{System.out.println(\"hello world\");}");
代码的第23行,把之前我们在方法里面设置好的东西全部添加到类当中去
ctClass.addMethod(ctMethod);
代码的第25-26行转化为class类然后进行实例化
Class<?> aClass = ctClass.toClass(); Object o = aClass.newInstance();
代码的27-28行获取方法并执行
Method method = aClass.getDeclaredMethod("execute"); method.invoke(o);
执行代码可以看到输出hello world
添加成员属性
package ysoserial; import javassist.ClassPool; import javassist.CtClass; import javassist.CtField; import java.lang.reflect.Modifier; public class Test { public static void main(String[] args) throws Exception { ClassPool classPool = ClassPool.getDefault(); CtClass ctClass = classPool.makeClass("ysoserial.JavassistTest"); //添加一个int类型的age变量 CtField ctField = new CtField(CtClass.intType,"age",ctClass); ctField.setModifiers(Modifier.PRIVATE); ctClass.addField(ctField); //添加一个String类型的name变量 CtField ctField2 = new CtField(classPool.getCtClass("java.lang.String"), "name" , ctClass); ctField2.setModifiers(Modifier.PUBLIC); ctClass.addField(ctField2); Class aClass = ctClass.toClass(); ctClass.writeFile(); Object obj = aClass.newInstance(); } }
添加构造方法
package ysoserial; import javassist.ClassPool; import javassist.CtClass; import javassist.CtConstructor; import javassist.CtField; import java.lang.reflect.Modifier; public class Test { public static void main(String[] args) throws Exception { ClassPool classPool = ClassPool.getDefault(); CtClass ctClass = classPool.makeClass("ysoserial.JavassistTest"); //添加一个int类型的age变量 CtField ctField = new CtField(CtClass.intType,"age",ctClass); ctField.setModifiers(Modifier.PRIVATE); ctClass.addField(ctField); //添加一个String类型的name变量 CtField ctField2 = new CtField(classPool.getCtClass("java.lang.String"), "name" , ctClass); ctField2.setModifiers(Modifier.PUBLIC); ctClass.addField(ctField2); //添加有参构造 CtConstructor ctConstructor1 = new CtConstructor(new CtClass[]{ CtClass.intType, classPool.getCtClass("java.lang.String")},ctClass); ctConstructor1.setModifiers(Modifier.PUBLIC); ctConstructor1.setBody("{$0.age = $1;$0.name = $2;}"); ctClass.addConstructor(ctConstructor1); Class aClass = ctClass.toClass(); } }