javassist
关于 Javassist 的学习笔记。
# start
javassist 是一个分析、编辑、创建Java字节码的类库,与 ASM 相比提供了更加方便的API,无需再去关注底层的栈操作和字节码。
依赖导入:
<dependencies>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.27.0-GA</version>
</dependency>
</dependencies>
# API
类 | 描述 |
---|---|
ClassPool | ClassPool是一个存储CtClass的容器,如果调用get 方法会搜索并创建一个表示该类的CtClass对象 |
CtClass | CtClass表示的是从ClassPool获取的类对象,可对该类就行读写编辑等操作 |
CtMethod | 可读写的类方法对象 |
CtConstructor | 可读写的类构造方法对象 |
CtField | 可读写的类成员变量对象 |
javassist 提供的API和反射非常类似,基本可以对应反射中的Class
、Method
、Constructor
、Field
。
此外 javassist 还提供了一些内置的标识符来表示一些特定的含义:
表达式 | 描述 |
---|---|
$0, $1, $2, ... | this 和方法参数 |
$args | Object[] 类型的参数数组 |
$$ | 所有的参数,如m($$) 等价于m($1,$2,...) |
$cflow(...) | cflow变量 |
$r | 返回类型,用于类型转换 |
$w | 包装类型,用于类型转换 |
$_ | 方法返回值 |
$sig | 方法签名,返回java.lang.Class[] 数组类型 |
$type | 返回值类型,java.lang.Class 类型 |
$class | 当前类,java.lang.Class 类型 |
# usage
相比于基于访问者模式的 ASM 框架来说,使用 javassist 来操作字节码是非常简单的。
# 读取类、方法、成员变量信息
通过 ClassPool
来获取 CtClass
对象之后,基本就和反射一样来读取类的信息。
package src;
public class TestClass {
private String name;
public TestClass() {
}
public TestClass(String name) {
this.name = name;
}
public String getId() {
return name;
}
public void setId(String name) {
this.name = name;
}
public void hello() {
System.out.println("Hello " + this.name);
}
}
package src;
import javassist.*;
import java.util.Arrays;
public class Main {
public static void main(String[] args) throws Exception {
ClassPool classPool = ClassPool.getDefault();
CtClass ctClass = classPool.getCtClass(TestClass.class.getName());
// 获取成员变量
CtField[] fields = ctClass.getDeclaredFields();
// 获取方法
CtMethod[] methods = ctClass.getDeclaredMethods();
// 获取构造方法
CtConstructor[] constructors = ctClass.getDeclaredConstructors();
// 输出信息
System.out.println("------fields------");
Arrays.stream(fields).forEach(System.out::println);
System.out.println("------methods------");
Arrays.stream(methods).forEach(System.out::println);
System.out.println("------constructors------");
Arrays.stream(constructors).forEach(System.out::println);
}
}
// output
------fields------
src.TestClass.name:Ljava/lang/String;
------methods------
javassist.CtMethod@fb809fd2[public getId ()Ljava/lang/String;]
javassist.CtMethod@db3679d4[public setId (Ljava/lang/String;)V]
javassist.CtMethod@30063153[public hello ()V]
------constructors------
javassist.CtConstructor@46d56d67[public TestClass ()V]
javassist.CtConstructor@d8355a8[public TestClass (Ljava/lang/String;)V]
# 修改类的方法
通过调用 CtMethods
的一些方法就可以进行方法逻辑的修改:
- setModifiers:修改方法的修饰符
- insertBefore:在方法执行前插入代码
- insertAfter:方法执行后插入代码
- setBody:修改整段方法的代码
- ······
package src;
import javassist.*;
import java.io.File;
import java.io.FileOutputStream;
public class Main {
public static void main(String[] args) throws Exception {
ClassPool classPool = ClassPool.getDefault();
CtClass ctClass = classPool.getCtClass(TestClass.class.getName());
// 获取 hello 方法
CtMethod hello = ctClass.getDeclaredMethod("hello", null);
// 修改方法的修饰符
hello.setModifiers(Modifier.PRIVATE);
// 实现简单AOP
hello.insertBefore("System.out.println(\"func start\");");
hello.insertAfter("System.out.println(\"func over\");");
// 写入class文件
byte[] bytecode = ctClass.toBytecode();
File file = new File("target/classes/src/TestClass.class");
FileOutputStream fos = new FileOutputStream(file);
fos.write(bytecode);
fos.flush();
fos.close();
}
}
修改后的字节码:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package src;
public class TestClass {
private String name;
public TestClass() {
}
public TestClass(String name) {
this.name = name;
}
public String getId() {
return this.name;
}
public void setId(String name) {
this.name = name;
}
private void hello() {
System.out.println("func start");
System.out.println("Hello " + this.name);
Object var2 = null;
System.out.println("func over");
}
}
修改成员变量信息方法上大体差不多。
# 动态生成类
使用CtClass
的makeClass
方法就可以动态生成一个类。
package src;
import javassist.*;
import java.io.File;
import java.io.FileOutputStream;
public class Main {
public static void main(String[] args) {
ClassPool classPool = ClassPool.getDefault();
CtClass ctClass = classPool.makeClass("src.ClassMade");
try {
// 加入静态代码块
ctClass.makeClassInitializer().insertBefore("java.lang.System.out.println(\"static code block\");");
// 创建成员变量
CtField id = CtField.make("private static int id = 3;", ctClass);
ctClass.addField(id);
// 创建方法
CtMethod main = CtMethod.make(
"public static void main(String[] args) {java.lang.System.out.println(id);}",
ctClass
);
ctClass.addMethod(main);
// 写入class文件
byte[] bytecode = ctClass.toBytecode();
File file = new File("target/classes/src/ClassMade.class");
if (!(file.exists())) {
file.createNewFile();
}
FileOutputStream fos = new FileOutputStream(file);
fos.write(bytecode);
fos.flush();
fos.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
如果要使用这个类的话,需要向 JVM 中动态注册,这个就是类加载中的内容了。
- 02
- CommonsBeanUtils04-19
- 03
- 基于Tomcat全局存储进行回显04-16