javassist全称(Java Programming Assistant),是一款比JDK内置库操作字节码更方便的代码库。为了方便实验,先引入它的依赖
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.21.0-GA</version>
</dependency>
什么是字节码呢?字节码就是一套可以被JVM处理的指令集,JVM将字节码指令转换为机器级汇编指令。举例如下:
创建一个User类,并用javac编译后得到User.class
public class User {
private String name;
private int age;
public void baseinfo(String name, int age){
this.name = name;
this.age = age;
}
}
然后 javap -c User.class 输出下面的字节码(java version "1.8.0_181")
public class bluesky.javassist.User {
public bluesky.javassist.User();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public void baseinfo(java.lang.String, int);
Code:
0: aload_0
1: aload_1
2: putfield #2 // Field name:Ljava/lang/String;
5: aload_0
6: iload_2
7: putfield #3 // Field age:I
10: return
}
aload_0指令将this指针入栈,aload_1将name变量入栈,putfield是实例字段访问指令,此处就是设置name字段。javassist可以帮助我们非常方便的操作这些字节码。
使用javassist生成一个类:
// 创建一个ClassFile对象
ClassFile cf = new ClassFile(false, "bluesky.javassist.JavassistGeneratedClass", null);
// 设置JavassistGeneratedClass实现Cloneable接口
cf.setInterfaces(new String[] {"java.lang.Cloneable"});
// 添加id属性
FieldInfo f = new FieldInfo(cf.getConstPool(), "id", "I");
// 属性的修饰符
f.setAccessFlags(AccessFlag.PUBLIC);
cf.addField(f);
ClassPool classPool = ClassPool.getDefault();
// 生成JavassistGeneratedClass类并获取它的所有属性
Field[] fields = classPool.makeClass(cf).toClass().getFields();
// 断言是否存在id属性
assertEquals(fields[0].getName(), "id");
获取方法baseinfo的指令:
ClassPool cp = ClassPool.getDefault();
ClassFile cf = cp.get("bluesky.javassist.User")
.getClassFile();
MethodInfo minfo = cf.getMethod("baseinfo");
CodeAttribute ca = minfo.getCodeAttribute();
CodeIterator ci = ca.iterator();
List<String> operations = new LinkedList<>();
while (ci.hasNext()) {
int index = ci.next();
int op = ci.byteAt(index);
operations.add(Mnemonic.OPCODE[op]);
}
assertEquals(operations,
Arrays.asList(
"aload_0",
"aload_1",
"putfield",
"aload_0",
"iload_2",
"putfield",
"return"));
添加构造方法:
ClassFile cf = ClassPool.getDefault().get("bluesky.javassist.User").getClassFile();
Bytecode code = new Bytecode(cf.getConstPool());
code.addAload(0);
code.addInvokespecial("java/lang/Object", MethodInfo.nameInit, "()V");
code.addReturn(null);
MethodInfo minfo = new MethodInfo(cf.getConstPool(), MethodInfo.nameInit, "()V");
minfo.setCodeAttribute(code.toCodeAttribute());
cf.addMethod(minfo);
在方法指定为止插入自己需要的逻辑:
ClassPool classPool = ClassPool.getDefault();
CtClass ctClass = classPool.get("bluesky.javassist.User");
CtMethod declaredMethod = ctClass.getDeclaredMethod("baseinfo");
declaredMethod.insertBefore("System.out.println(\"方法执行的第一行\")");
declaredMethod.insertAfter("System.out.println(\"方法return前\")");
User instance = (User) ctClass.toClass().newInstance();
instance.baseinfo("hello", 1);
通常可以使用字节码技术实现动态代理、热加载、debug、结合agent实现应用指标监控等。