Javassist 学习
环境搭建
这个非常简单,只需要你自己加一个依赖
<!-- https://mvnrepository.com/artifact/javassist/javassist --><dependency><groupId>javassist</groupId><artifactId>javassist</artifactId><version>3.12.1.GA</version></dependency>
简单了解
Java programming ASSISTant,Java编程助手。是Java中编辑字节码的类库,它使Java程序能够在运行时定义一个新类,并在JVM加载时修改类文件。
Java中所有的类都被编译为class文件来运行,在编译完class文件之后,类不能再被显示修改,而Javassist就是用来处理编译后的class文件,它可以用来修改方法或者新增方法,并且不需要深入了解字节码,还可以生成一个新的类对象。
ClassPool这个类是javassist的核心组件之一。ClassPool是CtClass对象容器,CtClass对象必须从该对象获得。
一些基础的语法
从上文的demo中可以看到部分使用方法,在javassist中CtClass代表的就是类class,ClassPool就是CtClass的容器,ClassPool维护了所有创建的CtClass对象,需要注意的是当CtClass数量过大会占用大量内存,需要调用CtClass.detach()释放内存。
ClassPool重点有以下几个方法:
- getDefault() 单例获取ClassPool
- appendClassPath() 将目录添加到ClassPath
- insertClassPath() 在ClassPath插入jar
- get() 根据名称获取CtClass对象
- toClass() 将CtClass转为Class 一旦被转换则不能修改
- makeClass() 创建新的类或接口
更多移步官方文档:http://www.javassist.org/html/javassist/ClassPool.html
CtClass需要关注的方法:
- addConstructor() 添加构造函数
- addField() 添加字段
- addInterface() 添加接口
- addMethod() 添加方法
- freeze() 冻结类使其不能被修改
- defrost() 解冻使其能被修改
- detach() 从ClassPool中删除类
- toBytecode() 转字节码
- toClass() 转Class对象
- writeFile() 写入.class文件
- setModifiers() 设置修饰符
移步:http://www.javassist.org/html/javassist/CtClass.html
CtMethod继承CtBehavior,需要关注的方法:
- insertBefore 在方法的起始位置插入代码
- insterAfter 在方法的所有 return 语句前插入代码
- insertAt 在指定的位置插入代码
- setBody 将方法的内容设置为要写入的代码,当方法被 abstract修饰时,该修饰符被移除
- make 创建一个新的方法
简单使用
创建class
packagejavassist;importjava.io.IOException;publicclassCreate{publicstaticvoidmain(String[] args)throwsNotFoundException,CannotCompileException,IOException{ClassPool pool =ClassPool.getDefault();CtClass cc = pool.makeClass("javassist.Person");// 添加字段CtField name =newCtField(pool.get("java.lang.String"),"name", cc);
name.setModifiers(Modifier.PRIVATE);
cc.addField(name,CtField.Initializer.constant("LJL"));CtField age =newCtField(pool.get("int"),"age", cc);
age.setModifiers(Modifier.PRIVATE);
cc.addField(age,CtField.Initializer.constant(18));// 添加getter和setter方法
cc.addMethod(CtNewMethod.getter("getName", name));
cc.addMethod(CtNewMethod.getter("getAge", age));
cc.addMethod(CtNewMethod.setter("setName", name));
cc.addMethod(CtNewMethod.setter("setAge", age));// 添加构造函数CtConstructor cons =newCtConstructor(newCtClass[]{}, cc);
cons.setBody("{name = \"nn0nkey\";}");
cc.addConstructor(cons);CtConstructor cons1 =newCtConstructor(newCtClass[]{pool.get("java.lang.String"), pool.get("int")}, cc);
cons1.setBody("{name = $1; age = $2;}");
cc.addConstructor(cons1);// 添加toString方法CtMethod toString =newCtMethod(pool.get("java.lang.String"),"toString",newCtClass[]{}, cc);
toString.setModifiers(Modifier.PUBLIC);
toString.setBody("{return \"name: \" + name + \", age: \" + age;}");
cc.addMethod(toString);// 将类写入文件
cc.writeFile("F:\\IntelliJ IDEA 2023.3.2\\java脚本\\CC1_5_6_7\\target\\classes");}}
需要注意的是在setBody()中我们使用了
$
符号代表参数
// $0代表this $1代表第一个传入的参数 类推
printName.setBody("{System.out.println($0.name);}");
body中代码该怎么写就怎么写
我们运行就可以生成我们的class文件
还是非常完美的
使用CtClass对象
这里一共有三种方法
反射调用
publicclassUseit{publicstaticvoidmain(String[] args)throwsNotFoundException,CannotCompileException,InstantiationException,IllegalAccessException,NoSuchMethodException,InvocationTargetException{ClassPool classPool=ClassPool.getDefault();CtClass cc=classPool.get("javassist.Person");Object person = cc.toClass().newInstance();Method setName=person.getClass().getDeclaredMethod("setName",String.class);
setName.setAccessible(true);
setName.invoke(person,"k1n9");Method tostring=person.getClass().getMethod("toString");Object result = tostring.invoke(person);System.out.println(result);}}
看代码就很容易看明白就是获取class后实例化,然后步骤就和反射一样了
加载class文件
这个和上面只能说大差不差,就是修改了获取class的方法
ClassPool pool =ClassPool.getDefault();
pool.appendClassPath("F:\\IntelliJ IDEA 2023.3.2\\java脚本\\CC1_5_6_7\\target\\classes");// 获取上面生成的类CtClass ctClass = pool.get("javassist.Person");Object person = ctClass.toClass().newInstance();// ...... 下面和通过反射的方式一样去使用
需要注意的点是appendClassPath中填入目录,不要具体的文件
通过接口的方式
这个方法是最方便的,因为反射确实太麻烦;我们考虑为该类写一个接口
//// Source code recreated from a .class file by IntelliJ IDEA// (powered by FernFlower decompiler)//packagejavassist;publicinterfacePersonI{voidsetName(String var1);voidsetAge(int var1);StringgetName();intgetAge();StringtoString();}
然后我们只需要为我们的类实现这个接口,就可以直接调用这个方法了
ClassPool pool =ClassPool.getDefault();
pool.appendClassPath("F:\\IntelliJ IDEA 2023.3.2\\java脚本\\CC1_5_6_7\\target\\classes");// 获取接口CtClass codeClassI = pool.get("javassist.PersonI");// 获取上面生成的类CtClass ctClass = pool.get("javassist.Person");// 使代码生成的类,实现 PersonI 接口
ctClass.setInterfaces(newCtClass[]{codeClassI});// 以下通过接口直接调用 强转PersonI person =(PersonI)ctClass.toClass().newInstance();System.out.println(person.getName());
person.setName("llll");System.out.println(person.toString());
需要注意的就是一定要强转
修改现有的类对象
这个也是我们在实战中比较好用的一个方法
比如我们修改我们的toString方法
packagejavassist;importjavassist.ClassPool;importjavassist.CtClass;importjavassist.CtMethod;importjavassist.NotFoundException;importjavassist.CannotCompileException;importjava.io.IOException;publicclassRefresh{publicstaticvoidmain(String[] args)throwsNotFoundException,CannotCompileException,IOException{ClassPool pool =ClassPool.getDefault();// 获取 Person 类CtClass person = pool.getCtClass("javassist.Person");// 获取 toString 方法CtMethod toString =newCtMethod(pool.get("java.lang.String"),"toString",newCtClass[]{}, person);// 修改 toString 方法的函数体
toString.setBody("{return \"Are you joker?\";}");// 将修改后的类写入文件(可选)
person.writeFile("F:\\IntelliJ IDEA 2023.3.2\\java脚本\\CC1_5_6_7\\target\\classes");}}
也是成功了好吧
版权归原作者 nn0nkey k1n9 所有, 如有侵权,请联系我们删除。