Javassist基本用法

article/2025/9/15 5:45:59

Javassist概述

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

Java常用到的动态特性主要是反射,在运行时查找对象属性、方法,修改作用域,通过方法名称调用方法等。在线的应用不会频繁使用反射,因为反射的性能开销较大。其实还有一种和反射一样强大的特性,但是开销却很低,它就是Javassit。

核心API

Javassist中每个需要编辑的class都对应一个CtCLass实例,CtClass的含义是编译时的类(compile time class),这些类会存储在ClassPool中(ClassPool是一个存储CtClass对象的容器)。

CtClass中的CtFieldCtMethod分别对应Java中的字段和方法。通过CtClass对象即可对类新增字段和修改方法等操作了。

ClassPool

ClassPool是 CtClass 对象的容器。它按需读取类文件来构造 CtClass 对象,并且保存 CtClass 对象以便以后使用。

  • getDefault:返回默认ClassPath 下的 ClassPool是 单例模式,一般通过该方法创建我们的ClassPool;
  • appendClassPath, insertClassPath:将一个 ClassPath 加到类搜索路径的末尾位置或插入到起始位置。通常通过该方法写入额外的类搜索路径,以解决多个类加载器环境中找不到类的尴尬。
  • toClass将修改后的CtClass加载至当前线程的上下文类加载器中,CtClass的toClass方法是通过调用本方法实现。需要注意的是一旦调用该方法,则无法继续修改已经被加载的class。
  • get,getCtClass:根据类路径名获取该类的CtClass对象。
  • makeClass:创建新类。
  • makeInterface :创建新接口。接口中的成员方法可以在 CtNewMethod 中使用 abstractMethod () 创建。

需要注意的是 ClassPool 会在内存中维护所有被它创建过的 CtClass,当 CtClass 数量过多时,会占用大量的内存,API中给出的解决方案是 有意识的调用 CtClass 的 detach() 方法以释放内存。

public class TestBean2 {public static void main(String[] args) {//获取默认ClassPath 下的 ClassPoolClassPool pool = ClassPool.getDefault();//插入ClassPath加到类搜索路径ClassLoader cl = Thread.currentThread().getContextClassLoader();pool.insertClassPath(new LoaderClassPath(cl));try {//获取指定类CtClassCtClass ctClass = pool.get("com.ymqx.动态增加属性和注解.TargetBean");} catch (NotFoundException e) {e.printStackTrace();}//创建新类CtClass newCtClass = pool.makeClass("com.ymqx.动态增加属性和注解.DestBean");//将修改后的CtClass加载至当前线程的上下文类加载器中try {pool.toClass(newCtClass);} catch (CannotCompileException e) {e.printStackTrace();}}
}

静态方法 ClassPool.getDefault () 返回的默认 ClassPool 将搜索底层 JVM (Java 虚拟机) 具有的同一路径。如果某个程序在 web 应用程序服务器 (如 JBoss 和 Tomcat) 上运行, 则 ClassPool 对象可能无法找到用户类, 因为这样的 web 应用程序服务器使用多个类加载器以及系统类加载程序。在这种情况下, 必须将附加的类路径注册到 ClassPool。

CtClass

CtClass 是类文件的抽象表示形式,CtClass 对象是处理类文件的句柄。

  • isFrozen:判断一个类是否已被冻结。
  • isModified:如果类被修改过的话就返回true,否则返回false。
  • isPrimitive:是否为Java原始类型:boolean, byte, char, short, int, long, float, double, or void。
  • prune:删除类不必要的属性,以减少内存占用。调用该方法后,许多方法无法将无法正常使用,慎用。
  • freeze:冻结一个类,使其不可修改。
  • defrost:解冻一个类,使其可以被修改。如果事先知道一个类会被defrost, 则禁止调用 prune 方法。
  • detach: 将该class从ClassPool中删除。
  • getName:获取CtClass代表的类的全限定类名。
  • setName:修改类名,必须以全限定类名的形式传递参数。
  • isInterfaceisAnnotationisEnum:CtClass代表的类对象是否为接口、注解、枚举类型。
  • getFieldgetMethodgetConstructor获取类的字段、方法、构造函数。
  • getFieldsgetMethodsgetConstructors获取类的字段、方法、构造函数的集合。
  • addFieldaddMethodaddConstructor:类中添加字段、方法、构造函数。
  • removeFieldremoveMethodremoveConstructor:从类中移除字段、方法、构造函数。
  • insturment:向类中注册CodeConverter或者ExprEditor,其中CodeConverter用于代码的转换,ExprEditor用于编辑方法体中满足条件的代码块,instrument(ExprEditor)是CtMethod和CtConstructor调用的方法。
  • toClasstoBytecodewriteFile:将CtClass对象转换为Class对象、bytecode字节码、写入.class文件中,调用这些方法后,CtClass对象将会被冻结,无法进行修改。
  • setSuperclass:设置父类。

CtField

CtField代表类的字段,既可以通过CtClass获取类中已经存在的字段,也可以新建一个CtField对象并添加到CtClass中。

  • make(String src, CtClass declaring):通过源码的形式构造CtField对象,例如src可以为:“public String name;”,不要忘记";",否则会编译失败,declaring代表我们要添加字段的类
  • getName:获取字段名
  • setName:修改字段名
  • getModifierssetModifiers:获取访问修饰符、设置访问修饰符,其中,设置访问修饰符可以通过Modifier中提供的常量字段设置,并且可以组合设置,例如要设置public static,可以使用如下设置:Modifier.PUBLIC | Modifier.STATIC
  • getAnnotationgetAnnotations:获取字段上的注解
  • getSignature:返回代表字段类型的字符串,如果是String类型的话,返回结果为:Ljava/lang/String;
  • getGenericSignaturesetGenericSignature:获取和设置字段的泛型类型
  • getConstantValue:只能获取Java基本类型的包装类型对应的常量值,如果是其他类型的常量的话则会返回null
  • getFieldInfo:获取字段信息,用于获取、修改、新增注解

在向CtClass类中添加新字段的时候,可以添加字段的初始值,在调用CtClass.addField方法时,向其中传入CtField.Initializer参数即可设置字段对应的初始值,例如:

CtField username = new CtField(pool.get("java.lang.String"), "username", ctClass);
username.setModifiers(Modifier.PRIVATE);ctClass.addField(username, CtField.Initializer.constant("ZhangSan"));

示例1:创建新类,添加一个int类型的,名字为value的变量,以及getter/setter

public class TestBean2 {public static void main(String[] args) {//获取默认ClassPath 下的 ClassPoolClassPool pool = ClassPool.getDefault();// 创建一个新类CtClass ctClass = pool.makeClass("com.ymqx.动态增加属性和注解.CreateBean");//添加一个int类型的,名字为value的变量,以及getter/settertry {CtField ctField = new CtField(CtClass.intType,"value",ctClass);ctField.setModifiers(Modifier.PRIVATE);ctClass.addField(ctField);//使用CtNewMethod添加getter/setterCtMethod getter = CtNewMethod.getter("getValue", ctField);CtMethod setter = CtNewMethod.setter("setValue", ctField);ctClass.addMethod(getter);ctClass.addMethod(setter);} catch (CannotCompileException e) {e.printStackTrace();}//生成字节码文件,方便查看创建出来的类的结果try {ctClass.writeFile(System.getProperty("user.dir") + "\\target\\classes");} catch (CannotCompileException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}
}

运行会生成新的.class文件

package com.ymqx.动态增加属性和注解;public class CreateBean {private int value;public int getValue() {return this.value;}public void setValue(int var1) {this.value = var1;}public CreateBean() {}
}

CtConstructor

CtConstructor代表类的构造函数,也可能代表类的初始化函数;可以通过isClassInitializer()方法确定是否为类初始化函数。

  • getLongName:获取带有参数的名称(全限定类名和构造函数参数类型),如:javassist.CtConstructor(CtClass[],CtClass)
  • getName:只获取构造函数名也就是类名
  • isConstructor:是否为构造函数
  • isClassInitializer:是否为类的初始化函数
  • callSuper:是否调用父类构造函数
  • setBody:设置构造函数源码
  • insertBeforeBody:在构造函数开头插入代码
  • setModifiers:设置访问权限

通过上述方法,我们可以自定义构造函数并添加到CtClass对象中去,也可以访问现有构造函数的相关信息。如果需要创建CtConstructor对象,可以通过 CtNewConstructor

  • make:构造CtNewConsturctor对象
  • copy:利用原有的CtConstructor对象复制一个新的CtConstructor对象
  • defaultConstructor:创建默认构造函数
  • skeleton:创建一个带有参数的构造函数,但是该函数体只调用super()方法,其余代码需要自己编写插入
// 方法一,通过new CtConstructor()的方式创建CtConstructor对象
// new CtClass[]{}相当于构造函数的参数,ctClass为要添加构造函数的类对象
CtConstructor none = new CtConstructor(new CtClass[] {}, ctClass);
none.setBody("System.out.println(\"使用javassist产生的默认构造函数\");");
ctClass.addConstructor(none);// 方法二,通过CtNewConstructor提供的静态方法创建CtConstructor对象
CtNewConstructor.make("public User() {System.out.println(\"使用javassist产生的默认构造函数\");}", ctClass);

CtMethod

CtMthod代表类中的某个方法,可以通过CtClass提供的API获取或者CtNewMethod新建,通过CtMethod对象可以实现对方法的修改。

  • make:通过源码的形式创建CtMethod对象
  • getName:获取函数名
  • setName:设置函数名(可以用于修改函数名称)
  • setBody:设置函数体
  • setWrappedBody:通过提供的CtMethod对象的函数体来设置当前CtMethod对象的函数体
  • setModifiers:设置函数的访问修饰符
  • insertBeforeinsertAfterinsertAt:在函数最开始 | 结束位置 | 任意位置(需要知道代码行号)插入代码
  • addLocalVariable:添加局部变量
  • addCatch:添加try catch语句

编译的时候,会把变量名抹掉,传递的参数会依次在局部变量表中的顺序。
比如,如果方法体是 public void test(int a,int b,int c){ … } ,那么a,b,c就对应本地变量表中的1,2,3的位置。Javassist中获取变量时就不能使用原始的名字,参数使用的是$1,$2,$3。

还可以通过 CtNewMethod生成方法

  • make:通过源码构造CtMethod对象
  • copy:利用原有的CtMethod对象复制一个新的CtMethod对象
  • abstractMethod:接口方法只能通过CtNewMethod.abstractMethod来创建抽象方法
  • getter:生成getter方法
  • setter:生成setter方法

示例2:创建新类,添加一个hello的方法

public class TestBean2 {public static void main(String[] args) {//获取默认ClassPath 下的 ClassPoolClassPool pool = ClassPool.getDefault();// 创建一个新类CtClass ctClass = pool.makeClass("com.ymqx.动态增加属性和注解.CreateBean");//添加一个hello的方法try {CtMethod ctMethod = new CtMethod(CtClass.intType, "hello", new CtClass[]{CtClass.intType, CtClass.doubleType}, ctClass);ctMethod.setModifiers(Modifier.PUBLIC);ctMethod.setBody("return $1 + $2;");//在方法体的前后分别插入代码ctMethod.insertBefore("System.out.println(\"在前面插入了:\" + $1);");ctMethod.insertAfter("System.out.println(\"在最后插入了:\" + $1);");ctClass.addMethod(ctMethod);} catch (CannotCompileException e) {e.printStackTrace();}//生成字节码文件,方便查看创建出来的类的结果try {ctClass.writeFile(System.getProperty("user.dir") + "\\target\\classes");} catch (CannotCompileException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}
}

运行会生成新的.class文件

package com.ymqx.动态增加属性和注解;public class CreateBean {public int hello(int var1, double var2) {System.out.println("在前面插入了:" + var1);double var5 = (double)var1 + var2;System.out.println("在最后插入了:" + var1);return (int)var5;}public CreateBean() {}
}

示例3:创建新类,使用CtNewMethod添加方法

public class TestBean2 {public static void main(String[] args) {//获取默认ClassPath 下的 ClassPoolClassPool pool = ClassPool.getDefault();// 创建一个新类CtClass ctClass = pool.makeClass("com.ymqx.动态增加属性和注解.CreateBean");try {//添加一个int类型的,名字为value的变量,以及getter/setterCtField ctField = new CtField(CtClass.intType,"value",ctClass);ctField.setModifiers(Modifier.PRIVATE);ctClass.addField(ctField);//使用CtNewMethod添加getter/setterCtMethod getter = CtNewMethod.getter("getValue", ctField);CtMethod setter = CtNewMethod.setter("setValue", ctField);ctClass.addMethod(getter);ctClass.addMethod(setter);//添加一个hello2的方法CtMethod ctMethod = CtNewMethod.make("public int hello2(int num1, double num2) {\n" +"    double sum = (double)num1 + num2;\n" +"    return (int)sum;\n" +"}", ctClass);ctClass.addMethod(ctMethod);} catch (CannotCompileException e) {e.printStackTrace();}//生成字节码文件,方便查看创建出来的类的结果try {ctClass.writeFile(System.getProperty("user.dir") + "\\target\\classes");} catch (CannotCompileException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}}
}

生成的 .class文件:

package com.ymqx.动态增加属性和注解;public class CreateBean {private int value;public int getValue() {return this.value;}public void setValue(int var1) {this.value = var1;}public int hello2(int var1, double var2) {double var4 = (double)var1 + var2;return (int)var4;}public CreateBean() {}
}

CtNewMethod.getter()和CtNewMethod.setter()更简洁生成getter/setter方法,make()方法生成的方法,变量名都会抹掉,替换成var1、var2等

Javassist中特殊参数示例

标识符作用
$0this
$1、$2、 …方法参数(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的正在修改的类

目前只用到简单方法,具体用法参考:
关于Java字节码编程javassist的详细介绍

ConstPool

创建属性相关实例时,比如,给字段属性增加注解等,都需要参数ConstPool常量池。

获取方式:

ClassPool pool = ClassPool.getDefault();//获取实体类
CtClass ctClass = pool.get("类名");
ConstPool constPool = ctClass.getClassFile().getConstPool();

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

相关文章

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)根据经验,通常情况下会报依赖关系的错误,我们可以使用以下的命令修复安…

dpkg制作deb包详解

1 deb包文件结构 deb 软件包里面的结构,它具有DEBIAN和软件具体安装目录(如etc, usr, opt, tmp等)。在DEBIAN目录中至少必须包括control文件,还有可能postinst(postinstallation)、postrm(postremove)、preinst(preinstallation)、…