[Java基础]—Javassist
创始人
2025-05-28 10:10:44

Javassist

Javassist (JAVA programming ASSISTant) 是在 Java 中编辑字节码的类库;它使 Java 程序能够在运行时定义一个新类, 并在 JVM 加载时修改类文件。原理与反射类似,但开销相对较低。

常用API

ClassPool

  • getDefault : 返回默认的 ClassPool 是单例模式的,一般通过该方法创建我们的 ClassPool;

  • appendClassPath, insertClassPath : 将一个 ClassPath 加到类搜索路径的末尾位置 或 插 入到起始位置。通常通过该方法写入额外的类搜索路径,以解决多个类加载器环境中 找不到类的尴尬;

  • get , getCtClass : 根据类路径名获取该类的 CtClass 对象,用于后续的编辑。

  • makeClass:创建一个新的类。

CtClass

  • freeze : 冻结一个类,使其不可修改;

  • isFrozen : 判断一个类是否已被冻结;

  • defrost : 解冻一个类,使其可以被修改。如果事先知道一个类会被 defrost, 则禁止 调用 prune 方法;

  • prune : 删除类不必要的属性,以减少内存占用。调用该方法后,许多方法无法将无 法正常使用,慎用;

  • detach : 将该 class 从 ClassPool 中删除;

  • writeFile : 根据 CtClass 生成 .class 文件;

  • toClass : 通过类加载器加载该 CtClass。

CtMethod

  • insertBefore : 在方法的起始位置插入代码;

  • insterAfter : 在方法的所有 return 语句前插入代码以确保语句能够被执行,除非遇到 exception;

  • insertAt : 在指定的位置插入代码;

  • setBody : 将方法的内容设置为要写入的代码,当方法被 abstract 修饰时,该修饰符被 移除;

  • make : 创建一个新的方法。

Javaassist 操作字节码示例

1、创建Hello类

public class Demo1 {public static void main(String[] args) throws NotFoundException, IOException, CannotCompileException {ClassPool cp = ClassPool.getDefault();CtClass ctClass = cp.makeClass("Javassist.Hello");ctClass.writeFile();}
}

在这里插入图片描述

2、添加属性

public static void main(String[] args) throws NotFoundException, IOException, CannotCompileException {//1、创建Hello类ClassPool cp = ClassPool.getDefault();CtClass ctClass = cp.makeClass("Javassist.Hello");//2、添加属性CtField name = new CtField(cp.get("java.lang.String"), "name", ctClass);name.setModifiers(Modifier.PUBLIC);ctClass.addField(name,CtField.Initializer.constant("Sentiment"));ctClass.writeFile();
}

在这里插入图片描述

属性赋值时也可用:

ctClass.addField(name,"name=\"Sentiment\"");

但这种赋值偏向于用构造器等进行初始化

3、添加方法

可以设置的返回类型:

public static CtClass booleanType;
public static CtClass charType;
public static CtClass byteType;
public static CtClass shortType;
public static CtClass intType;
public static CtClass longType;
public static CtClass floatType;
public static CtClass doubleType;
public static CtClass voidType;

这里可以发现不支持String类型,在Java字节码中,String类型在方法的参数列表和返回值类型中,通常不是直接使用字符串,而是使用字符串在常量池中的索引值。如果想设置String类型的话可以用:cp.getCtClass("java.lang.String")

CtMethod ctMethod = new CtMethod(CtClass.voidType, "Hello1", new CtClass[]{CtClass.intType, CtClass.charType}, ctClass);
ctMethod.setModifiers(Modifier.PUBLIC);
ctClass.addMethod(ctMethod);
ctClass.writeFile();

在这里插入图片描述

设置方法体

ctMethod.setBody("System.out.println(\"This is test !\");");

在方法体的前后分别插入代码

这里有参构造的形参是var1,如果要输出var1,就要用到特殊变量$1、$2(具体使用后边再说)

CtMethod ctMethod = new CtMethod(CtClass.voidType, "Hello1", new CtClass[]{CtClass.intType, CtClass.charType}, ctClass);
ctMethod.setModifiers(Modifier.PUBLIC);
ctMethod.setBody("System.out.println(\"This is test !\");");
ctClass.addMethod(ctMethod);
ctMethod.insertBefore("System.out.println(\"我在前面插入:\"+$1);");
ctMethod.insertAfter("System.out.println(\"我在后面插入了:\"+$2);");
ctClass.writeFile();

在这里插入图片描述

4、添加构造器

直接添加的有参构造,无参构造去掉中间的参数即可

CtConstructor cons = new CtConstructor(new CtClass[]{cp.getCtClass("java.lang.String")}, ctClass);
cons.setBody("{name=\"Sentiment\";}");
ctClass.addConstructor(cons);

设置name=var1

{$0.name = $1;}

在这里插入图片描述

5、修改已有类

可以通过ClassPool的get方法获取已有类,并进行修改

package Javassist;import javassist.*;import java.io.IOException;public class Demo02 {public static void main(String[] args) throws NotFoundException, CannotCompileException, IOException {ClassPool cp = ClassPool.getDefault();CtClass ctClass = cp.get("Javassist.Test");CtConstructor test = ctClass.getConstructors()[0];test.setBody("{System.out.println(\"Changing......\");}");ctClass.writeFile();}}
class Test {public static String name = "Sentiment";public Test() {System.out.println("This is test !");}
}

在这里插入图片描述

6、加载字节码

通过自带的toBytecode()转换下即可

ctClass.toBytecode();
ctClass.toClass().newInstance();

Javassist 特殊变量

标识符作用
$0、$1、$2、 3 、 3、 3、…this和方法参数(1-N是方法参数的顺序)
$args方法参数数组,类型为Object[]
$$所有方法参数,例如:m($$)相当于m($1,$2,…)
$cflow(…)control flow 变量
$r返回结果的类型,在强制转换表达式中使用。
$w包装器类型,在强制转换表达式中使用。
$_方法的返回值
$sig类型为java.lang.Class的参数类型对象数组
$type类型为java.lang.Class的返回值类型
$class类型为java.lang.Class的正在修改的类

1、$0,$1,$2,…

$0代表this,$1、$2代表方法的形参,通过上边例子也不难看出。这里需要注意:静态方法是没有$0的

2、$args

$args变量表示所有参数的数组,它是一个Object类型的数组(new Object[]{…}),如果参数中有原始类型的参数,会被转换成对应的包装类型。
在这里插入图片描述

3、$$

$$是方法所有参数的简写

public static void main(String[] args) throws NotFoundException, CannotCompileException, IOException {ClassPool cp = ClassPool.getDefault();CtClass ctClass = cp.makeClass("Javassist.SpecialVariables");CtMethod ctMethod = new CtMethod(CtClass.voidType, "Test1",new CtClass[]{CtClass.intType, CtClass.doubleType}, ctClass);ctMethod.setModifiers(Modifier.PUBLIC);ctMethod.setBody("System.out.println($args);");ctClass.addMethod(ctMethod);//Test2方法调用Test1CtMethod ctMethod1 = new CtMethod(CtClass.voidType, "Test2",new CtClass[]{CtClass.intType, CtClass.doubleType}, ctClass);ctMethod1.setModifiers(Modifier.PUBLIC);ctMethod1.setBody("Test1($$);");ctClass.addMethod(ctMethod1);ctClass.writeFile();
}

这里在定义一个Test2方法调用Test1,传参时写成Test1($$)就相当于Test1($1,$2)
在这里插入图片描述

剩下的遇到了再看吧。

Javassist 修改代码

Javassist 仅允许修改一个方法体中的表达式。javassist.expr.ExprEditor 是一个用来替换 方法体内表达式的类。用户可以定义 ExprEditor 的子类来制定表达式的修改

javassist.expr.MethodCall

当修改某个方法中的代码时,可以用MethodCall进行回环调用找到我们要改的函数,并通过replace()进行修改。

这里定义了一个print方法,并用到了print和println两个方法,之后通过MethodCall的getMethodName获取到该方法中调用的方法
在这里插入图片描述

所以这里可以做一个判断,当getMethodName等于print时既可以使用replace方法进行替换,由于只改方法不该参数,所以用$$直接代替原来的参数即可

package Javassist;import javassist.*;
import javassist.expr.ExprEditor;
import javassist.expr.MethodCall;import java.io.IOException;public class Demo04 {public static void main(String[] args) throws NotFoundException, CannotCompileException, IOException {ClassPool cp = ClassPool.getDefault();CtClass ctClass = cp.get("Javassist.Change");CtMethod ctMethod = ctClass.getDeclaredMethod("print");ctMethod.instrument(new ExprEditor(){public void edit(MethodCall m)throws CannotCompileException{if (m.getClassName().equals("java.io.PrintStream")&&m.getMethodName().equals("print")){m.replace("System.out.println($$);");}}});ctClass.writeFile();}
}
class Change {public static String name = "Sentiment";public void print() {System.out.println("This is one !");System.out.print("This is two !");}
}

在这里插入图片描述

javassist.expr.ConstructorCall

修改控制器的与上同理

参考链接

关于Java字节码编程javassist的详细介绍 | w3c笔记 (w3cschool.cn)

《宽字节安全》

相关内容

热门资讯

我来教教您「闽乐乐」为什么一直... 您好:闽乐乐这款游戏可以开挂,确实是有挂的,需要了解加客服微信【9307068】很多玩家在这款游戏中...
重大消息.熊猫炸金花是不是有挂... 重大消息.熊猫炸金花是不是有挂.(透视曝光猫腻)亲,熊猫炸金花这个游戏其实有挂的,确实是有挂的,需要...
重大通报“独角兽其实是有透视挂... 您好:独角兽这款游戏可以开挂,确实是有挂的,需要了解加客服微信【4194432】很多玩家在独角兽这款...
重大通报“博乐温州棋牌确实是有... 亲:博乐温州棋牌这款游戏是可以开挂的,确实是有挂的,添加客服【3671900】很多玩家在这款游戏中怀...
今日热点.万乐互娱开挂辅助神器... 今日热点.万乐互娱开挂辅助神器.(外卦神器下载)您好:万乐互娱这款游戏可以开挂,确实是有挂的,需要了...