Java字节码操作全解析:Javassist篇
发表时间: 2023-12-30 12:18
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实现应用指标监控等。