[Java基础]—Javassist

article/2025/9/15 5:31:11

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)

《宽字节安全》


http://chatgpt.dhexx.cn/article/B5jEvbsi.shtml

相关文章

Javassist基本用法

Javassist概述 Javassist是可以动态编辑Java字节码的类库。它可以在Java程序运行时定义一个新的类,并加载到JVM中;还可以在JVM加载时修改一个类文件,添加新的方法,或者是修改已有的方法。Javassist使用户不必关心字节码相关的规范…

Java中的高性能字节码工具:Javassist

前言 一般常见的动态方法调用使用Reflection或者字节码生成技术。虽然JDK已对反射进行了优化但在追求性能的场景中仍然显得性能不佳。本文即是介绍一个面向程序员友好的字节码操作类库javassist。根据benchmark其展现的性能已几乎无异于直接调用。 开源地址:javas…

Java字节码技术javassist

一、Javassist入门 (一)Javassist是什么 Javassist是可以动态编辑Java字节码的类库。它可以在Java程序运行时定义一个新的类,并加载到JVM中;还可以在JVM加载时修改一个类文件。Javassist使用户不必关心字节码相关的规范也是可以编…

java--javassist学习

Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的。它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态AOP框架。javassist是jb…

javassist使用指南

目录 一、快速入门1.1 创建class文件1.2 ClassPool的相关方法1.3 CtClass的相关方法1.4 CtMethod的相关方法1.5 调用生成的类对象1.5.1 通过反射调用1.5.2 通过接口调用 1.6 修改现有的类对象 二、将类冻结三、类搜索路径四、$开头的特殊字符五、ProxyFactory的使用 我们知道Ja…

systemd介绍

由来 Linux 的启动一直采用init进程,这种方法有两个缺点。一是启动时间长。init进程是串行启动,只有前一个进程启动完,才会启动下一个进程,二是启动脚本复杂。init进程只是执行启动脚本,不管其他事情。脚本需要自己处理…

systemd man手册

SYSTEMD(1)systemd SYSTEMD(1) 名称 systemd,init-systemd系统和服务管理器 概要 /lib/systemd/systemd [OPTIONS...]init [OPTIONS...] {COMMAND}描述 systemd是Linux操作系统的系统和服务管理器。在启动时作为PID的…

systemd wsl 测试笔记

文章目录 systemd 简介WSL systemdsystemctljournalctlhello serviceSleep 与 Timeout 测试Requires 测试After 测试 systemd 简介 Linux 从关闭到运行, 完整的启动和启动过程有三个主要部分: 硬件启动(Hardware boot): 初始化系统硬件Linux 引导(Linux boot): 加载 Linux 内核…

Linux systemd启动流程

以Ubuntu 18.04.2 LTS为例,列出Systemd启动target中涉及单元(Unit). default.target ( graphical.target by default) graphical.targetRequiresmulti-user.targetWantsdisplay-manager.serviceConflictsrescue.service rescue.targetAftermulti-user.target rescu…

关于 Linux中systemd的一些笔记

写在前面 嗯,准备RHCA,学习整理这部分知识博文内容涉及: systemd简述对于unit的信息的介绍通过systemctl命令控制Service unit的DemoService unit配置文件内容,权值的一些介绍 傍晚时分,你坐在屋檐下,看着天慢慢地黑下…

Linux-Systemd

目录 一、Systemd概述 二、Systemd优势 2.1兼容性 2.2启动速度 2.3systemd 提供按需启动能力 2.4采用 linux 的 cgroups 跟踪和管理进程的生命周期 2.5启动挂载点和自动挂载的管理 2.6实现事务性依赖关系管理 2.7日志服务 2.8 依赖关系 2.9systemd 事务 三、unit(单…

Systemd 简介

一 概述 Linux 服务管理有两种方式service和systemctl。而systemd是Linux系统最新的初始化系统(init),作用是提高系统的启动速度,尽可能启动较少的进程,尽可能更多进程并发启动,systemd对应的进程管理命令就是systemctl。值得一提…

systemd简介

Systemd 是一个专用于 Linux 操作系统的系统与服务管理器,其目的是要取代Unix时代以来一直在使用的init系统。 systemd概述Systemd 是 Linux 系统中最新的初始化系统(init),它主要的设计目标是克服 sysvinit 固有的缺点,提高系统的启动速度。 systemd框架图 根据 Linux 惯…

Systemd 入门及常用命令

目录 Systemdsystemd架构systemd 系统管理管理系统查看启动耗时查看当前主机的信息 Unit 资源查看当前系统的所有 Unit查看系统状态和单个 Unit 的状态 Unit 管理依赖关系Unit 配置文件查看配置文件的内容 Targettarget(Systemd) 与 runlevel&#xff08…

systemd 介绍

转自:https://www.linuxidc.com/Linux/2018-03/151291.htm 从 init 系统说起 Linux 操作系统的启动首先从 BIOS 开始,接下来进入 boot loader,由 bootloader 载入内核,进行内核初始化。内核初始化的最后一步就是启动 PID 为 1 的 …

[2020.1.10]systemd介绍

1 systemd基本概念 systemd:a system daemon,相当于以前的init进程,pid1,systemd是1号进程!!! sbin/init --> /lib/systemd/systemd unit: 一个进程,例如lightdm.service job: 一个动作,启动是个job,关闭是个job。开机启动时,systemd…

systemd教程(三)

下来会通过示例来描述不同Service Type值的应用场景。在此之前,强烈建议先阅读前后台进程父子关系和daemon类进程来搞懂进程之间的关系和Daemon类进程的特性。 systemd service:Typeforking 当使用systemd去管理一个长久运行的服务进程时,最…

dpkg打包笔记

dpkg 安装软件 dpkg -i xxx.deb 查看安装目录 dpkg -L xxx 显示版本 dpkg -l xxx 详细信息 dpkg -s xxx 罗列内容 dpkg -c xxxx 卸载软件 dpkg -r xxxx 构建deb包 dpkg -p

dpkg和apt

文章目录 Ubuntu软件安装Deb包安装Deb包简介dpkg常用命令 apt-get源安装apt-get简介apt工作原理apt相关文件常用的apt命令参数apt-getapt-cache dpkg和apt-get的区别 Ubuntu软件安装 Linux有很多种发行版本,各种发行版本之间安装软件方式和命令不一样,同…

dpkg安装

目录 转载:https://blog.csdn.net/u012300744/article/details/80267225 1.安装 (3)安装 sudo dpkg -i deb文件名 (4)根据经验,通常情况下会报依赖关系的错误,我们可以使用以下的命令修复安…