00-JAVA基础-javassist字节码操作
Javassist是一个开源的分析、编辑和创建Java字节码的类库。它是jboss的一个子项目,主要优点在于简单且快速。它允许开发者直接使用Java编码的形式,而无需了解虚拟机指令,就能动态地改变类的结构或动态生成类。Javassist提供了一组简单易用的API,使开发者能够动态地创建、修改、分析Java类,而无需关心底层的字节码细节。
字节码操作
什么是字节码
Java字节码(Java bytecode)是Java虚拟机(JVM)执行的一种虚拟指令格式。它是由Java编译器生成的,基于栈的指令集,用于在Java虚拟机上执行。字节码文件包含了JVM能够识别的指令,当JVM执行这些指令时,会通过解释器逐条读取字节码文件中的指令,并将其解释成机器码执行。
JAVA动态性的实现方式
- 字节码操作
- 反射
运行时操作字节码可以让我们实现如下功能:
- 动态生成新的类
- 动态改变某个类的结构(添加/删除/修改 新的属性/方法)
运行时操作字节码的优势:反射开销小,性能高
什么是Javassist
Javassist是一个开源的分析、编辑和创建Java字节码的类库。它是jboss的一个子项目,主要优点在于简单且快速。它允许开发者直接使用Java编码的形式,而无需了解虚拟机指令,就能动态地改变类的结构或动态生成类。
Javassist提供了一组简单易用的API,使开发者能够动态地创建、修改、分析Java类,而无需关心底层的字节码细节。
它的核心API包括ClassPool和CtClass,其中ClassPool用于跟踪和控制所操作的类,而CtClass则提供了检查类数据(如字段和方法)以及在类中添加新字段、方法和构造函数等功能。
Javassist的功能与jdk自带的反射功能类似,但更为强大。它能在Java程序运行时定义新的类并加载到JVM中,也可以在JVM加载时修改一个类文件。
此外,Javassist还用于生成新的Java类以适应不同的需求,例如在实现ORM框架时;或者用于编写Mock框架,以便在测试过程中控制类的行为;以及实现性能监控工具,对方法的执行时间进行统计和分析,诊断潜在问题。
Javassist常用API
Javassist是一个功能强大的Java字节码操作和分析库,它提供了一组常用的API来简化对字节码的修改和生成。以下是一些Javassist的常用API及其功能描述:
模块 | 方法 | 描述 |
---|---|---|
ClassPool模块 | - | - |
- | ClassPool.getDefault() | 获取默认的ClassPool对象,它用于存储和操作类信息。 |
- | pool.get(className) | 根据类名获取对应的CtClass对象。 |
- | pool.makeClass(name) | 创建一个新的类。 |
CtClass模块 | - | - |
- | ctClass.setName(name) | 设置类的名称。 |
- | ctClass.setSuperclass(superclass) | 设置类的父类。 |
- | ctClass.getDeclaredMethods() | 获取类声明的所有方法。 |
- | ctClass.getDeclaredFields() | 获取类声明的所有字段。 |
- | ctClass.makeClassFile() | 获取该类的ClassFile对象,用于更底层的字节码操作。 |
- | ctClass.writeFile() | 将修改后的类写入文件。 |
CtMethod模块 | - | - |
- | ctMethod.setName(name) | 设置方法名称。 |
- | ctMethod.setModifier(modifier) | 设置方法修饰符。 |
- | ctMethod.setBody(body) | 设置方法的字节码体。 |
- | ctMethod.insertBefore(src) | 在方法体前插入字节码。 |
- | ctMethod.insertAfter(src) | 在方法体后插入字节码。 |
CtField模块 | - | - |
- | ctField.setName(name) | 设置字段名称。 |
- | ctField.setType(type) | 设置字段类型。 |
- | ctField.setModifier(modifier) | 设置字段修饰符。 |
- | ctField.setInitialValue(value) | 设置字段的初始值。 |
CtConstructor | - | 与CtMethod类似,用于操作类的构造函数。 |
javassist.bytecode 模块 | - | ClassFile、MethodInfo、CodeAttribute等类提供了对字节码的低级别访问接口,允许直接读取和修改字节码指令。 |
其他 | - | - |
- | new Annotation() | 创建注解。 |
- | new Bytecode() | 直接操作字节码。 |
这些API只是Javassist功能的一部分,Javassist还提供了许多其他功能,如接口修改、异常处理、导入类等。使用时,建议参考Javassist的官方文档或相关教程以获取更详细的信息和示例代码
测试案例
案例1
package com.demo.demo1;
import javassist.*;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
/**
* 是用javassist创建一个UserDo,使用构造方法设置值,打印结果
*
* @author Anna.
* @date 2024/4/4 17:32
*/
public class JavassistDemo {
public static void main(String[] args) throws Exception {
// 创建ClassPool对象
ClassPool classPool = ClassPool.getDefault();
// 创建一个新的类
CtClass ctClass = classPool.makeClass("com.demo.entity.UserDo");
// 创建属性
CtField fieldName = CtField.make("private String name;", ctClass);
CtField fieldAge = CtField.make("private Integer age;", ctClass);
// 将属性添加到类中
ctClass.addField(fieldName);
ctClass.addField(fieldAge);
// 创建get方法
CtMethod getName = CtMethod.make("public String getName(){return this.name;}", ctClass);
CtMethod getAge = CtMethod.make("public Integer getAge(){return this.age;}", ctClass);
// 将方法添加到类中
ctClass.addMethod(getName);
ctClass.addMethod(getAge);
// 创建构造器
CtConstructor constructor1 = CtNewConstructor.make("public UserDo(){}", ctClass);
CtConstructor constructor2 = CtNewConstructor.make("public UserDo(String name,Integer age){this.name = name; this.age = age;}", ctClass);
// 添加构造器
ctClass.addConstructor(constructor1);
ctClass.addConstructor(constructor2);
// 写入对应文件
String path = JavassistDemo.class.getResource("/").getPath();
ctClass.writeFile(path);
// 使用反射获取对象并初始化,调用getName方法
URLClassLoader urlClassLoader = URLClassLoader.newInstance(new URL[]{new URL("file:/" + path)});
Class<?> clazz = urlClassLoader.loadClass("com.demo.entity.UserDo");
Object o1 = clazz.getDeclaredConstructor(String.class, Integer.class).newInstance("张三", 20);
// 调用getName方法
Method getName1 = clazz.getMethod("getName");
System.out.println(getName1.invoke(o1));
}
}
执行结果
案例2
package com.demo.demo2;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import java.lang.reflect.Method;
/**
* 1 通过Javassist 获取UserDo的class
* 2 在setName之前添加打印信息
* 3 修改getName第14行输出内容System.out.println("调用了getName方法"); 为System.out.println("通过avassist 修改了调用了getName方法");
*
* @author Anna.
* @date 2024/4/4 19:58
*/
public class JavassistDemo {
public static void main(String[] args) throws Exception {
// 创建ClassPool对象
ClassPool classPool = ClassPool.getDefault();
// 获取UserDo
CtClass ctClass = classPool.getCtClass("com.demo.demo2.UserDo");
// 在setName之前添加打印信息
// 获取调用setName方法
CtMethod setName = ctClass.getDeclaredMethod("setName", new CtClass[]{classPool.get("java.lang.String")});
// 设置方法调用前打印信息 $0 表示this,$1表示第一个参数
setName.insertBefore("System.out.println(\"javassiist在setName前设置打印:\" + $1);");
// 获取调用getName方法
CtMethod getName = ctClass.getDeclaredMethod("getName");
// 在getName第14行插入输出内容为System.out.println("通过avassist 修改了调用了getName方法");
getName.insertAt(14, "System.out.println(\"通过avassist 修改了调用了getName方法\");");
getName.insertAfter("System.out.println(\"javassiist在getName执行结束后return前设置打印\" );");
// 通过反射调用新生成的方法
Class<?> clazz = ctClass.toClass();
// 初始化
Object o = clazz.getDeclaredConstructor().newInstance();
// 调用setName
Method setName1 = clazz.getMethod("setName", String.class);
setName1.invoke(o, "张三");
// 调用getName
Method getName1 = clazz.getMethod("getName");
System.out.println(getName1.invoke(o));
}
}
执行结果
gitee源码
更多推荐
所有评论(0)