[Java基础]—Javassist

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方法调用Test1
    CtMethod 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)

《宽字节安全》

  • 3
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值