Javassist(Java Programming Assistant)是一个开源的 Java 字节码操作库。它允许用户在运行时修改已有的 Java 类或者生成新的类。Javassist 提供了比 ASM 更高层次的 API,使得字节码操作变得更为简单和直观。
以下是使用 Javassist 的基本步骤和一些示例代码:
1. 添加依赖
对于 Maven 项目,可以在 pom.xml
文件中添加以下依赖:
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.29.0-GA</version>
</dependency>
对于 Gradle 项目,可以在 build.gradle
文件中添加以下依赖:
implementation 'org.javassist:javassist:3.29.0-GA'
对于非以上两种的集成方式,可以直接导入javassist.jar包,下载地址
2. 创建和修改类
以下是一些常见的使用场景和对应的示例代码:
创建一个新类
import javassist.*;
public class CreateClassExample {
public static void main(String[] args) {
try {
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.makeClass("org.example.Student");
//添加属性
CtField id = new CtField(CtClass.intType, "id", cc);
id.setModifiers(Modifier.PRIVATE);
cc.addField(id);
CtField name = new CtField(pool.get("java.lang.String"), "name", cc);
name.setModifiers(Modifier.PRIVATE);
cc.addField(name);
CtField age = new CtField(CtClass.intType, "age", cc);
age.setModifiers(Modifier.PRIVATE);
cc.addField(age);
//添加构造方法
CtConstructor ctConstructor = new CtConstructor(new CtClass[]{CtClass.intType, pool.get("java.lang.String"), CtClass.intType}, cc);
ctConstructor.setBody("{this.id = $1;this.name = $2;this.age = $3;}");
cc.addConstructor(ctConstructor);
//添加方法
CtMethod getId = new CtMethod(CtClass.intType, "getId", new CtClass[]{}, cc);
getId.setBody("{return this.id;}");
cc.addMethod(getId);
CtMethod setId = new CtMethod(CtClass.voidType, "setId", new CtClass[]{CtClass.intType}, cc);
setId.setBody("{this.id = $1;}");
cc.addMethod(setId);
CtMethod getName = new CtMethod(pool.get("java.lang.String"), "getName", new CtClass[]{}, cc);
getName.setBody("{return this.name;}");
cc.addMethod(getName);
CtMethod setName = new CtMethod(CtClass.voidType, "setName", new CtClass[]{pool.get("java.lang.String")}, cc);
setName.setBody("{this.name = $1;}");
cc.addMethod(setName);
CtMethod getInfo = new CtMethod(pool.get("java.lang.String"), "getInfo", new CtClass[]{CtClass.intType, pool.get("java.lang.String"), CtClass.intType}, cc);
getInfo.setBody("{ return new StringBuilder(\"Student:{\").append(\"id:\").append($1).append(\",name:\").append($2).append(\",age:\").append($3).append(\"}\").toString(); }");
getInfo.setModifiers(Modifier.PRIVATE);
cc.addMethod(getInfo);
//写入文件
cc.writeFile("./output");
//使用生成的类
Class<?> clazz = cc.toClass();
Object obj = clazz.getConstructor(int.class, String.class, int.class).newInstance(1, "vesper", 12);
System.out.println("改之前:name="+clazz.getMethod("getName").invoke(obj));
clazz.getMethod("setName", String.class).invoke(obj, "alice");
System.out.println("改之后:name="+clazz.getMethod("getName").invoke(obj));
System.out.println("改之前:id="+clazz.getMethod("getId").invoke(obj));//读id
clazz.getMethod("setId", int.class).invoke(obj, 24);//修改id
System.out.println("改之后:id="+clazz.getMethod("getId").invoke(obj));//读id
//测试使用私有的方法
Method method = clazz.getDeclaredMethod("getInfo", int.class, String.class, int.class);
method.setAccessible(true);
System.out.println(method.invoke(obj, 12, "123", 22));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
修改已有类
import javassist.*;
public class ModifyClassExample {
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("com.example.ExistingClass");
// 修改已有方法
CtMethod method = cc.getDeclaredMethod("existingMethod");
method.insertBefore("{ System.out.println(\"Before method call\"); }");
method.insertAfter("{ System.out.println(\"After method call\"); }");
// 添加新方法
CtMethod newMethod = new CtMethod(CtClass.voidType, "newMethod", new CtClass[]{}, cc);
newMethod.setBody("{ System.out.println(\"New method\"); }");
cc.addMethod(newMethod);
// 将修改后的类写入文件
cc.writeFile("./output");
// 使用修改后的类
Class<?> clazz = cc.toClass();
Object instance = clazz.getDeclaredConstructor().newInstance();
clazz.getMethod("existingMethod").invoke(instance);
clazz.getMethod("newMethod").invoke(instance);
}
}
3. 动态加载类
Javassist 还支持动态加载和修改类。例如,可以通过自定义类加载器来加载修改后的类:
import javassist.*;
public class DynamicClassLoaderExample {
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.makeClass("com.example.DynamicClass");
// 添加方法
CtMethod method = new CtMethod(CtClass.voidType, "dynamicMethod", new CtClass[]{}, cc);
method.setBody("{ System.out.println(\"Dynamic method\"); }");
cc.addMethod(method);
// 使用自定义类加载器加载类
ClassLoader loader = new ClassLoader() {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] b = cc.toBytecode();
return defineClass(name, b, 0, b.length);
} catch (Exception e) {
throw new ClassNotFoundException(e.toString());
}
}
};
Class<?> clazz = loader.loadClass("com.example.DynamicClass");
Object instance = clazz.getDeclaredConstructor().newInstance();
clazz.getMethod("dynamicMethod").invoke(instance);
}
}
4. 使用代理生成器
Javassist 还提供了一个简单的代理生成器,可以用来生成动态代理类:
import javassist.util.proxy.MethodHandler;
import javassist.util.proxy.ProxyFactory;
import javassist.util.proxy.ProxyObject;
public class ProxyExample {
public static void main(String[] args) throws Exception {
ProxyFactory factory = new ProxyFactory();
factory.setSuperclass(MyClass.class);
Class<?> proxyClass = factory.createClass();
MyClass proxyInstance = (MyClass) proxyClass.getDeclaredConstructor().newInstance();
((ProxyObject) proxyInstance).setHandler(new MethodHandler() {
@Override
public Object invoke(Object self, java.lang.reflect.Method thisMethod, java.lang.reflect.Method proceed, Object[] args) throws Throwable {
System.out.println("Before method call");
Object result = proceed.invoke(self, args);
System.out.println("After method call");
return result;
}
});
proxyInstance.myMethod(); // 调用代理方法
}
public static class MyClass {
public void myMethod() {
System.out.println("Inside method");
}
}
}
总结
Javassist 是一个强大的字节码操作库,提供了简洁的 API 来创建和修改 Java 类。通过上述示例代码,可以看到如何使用 Javassist 创建新类、修改已有类、动态加载类以及生成代理类。更多高级功能和详细文档可以参考 Javassist 官方文档。