如何使用Javassist将方法字节码复制修改后写入新方法
使用Javassist提取旧方法字节码并修改生成新方法
我来帮你搞定这个Javassist的字节码修改需求,其实步骤很清晰,咱们一步步来实现:
核心思路
先加载目标类,提取Method_old的字节码结构,移除不需要的指令(也就是bipush 20和istore_2这两步),然后把修改后的字节码封装成一个新的method_new方法,最后将修改后的类写入文件。
具体实现代码
首先确保你的项目已经引入了Javassist依赖(比如Maven添加javassist:javassist:3.29.2-GA),然后用以下代码完成操作:
import javassist.*; import javassist.bytecode.*; public class BytecodeModifier { public static void main(String[] args) throws Exception { // 1. 初始化类池,加载目标类(替换成你的类全限定名) ClassPool classPool = ClassPool.getDefault(); CtClass targetClass = classPool.get("com.yourpackage.YourClass"); // 2. 获取原方法Method_old CtMethod oldMethod = targetClass.getDeclaredMethod("Method_old"); // 3. 复制原方法生成新方法,命名为method_new CtMethod newMethod = CtNewMethod.copy(oldMethod, "method_new", targetClass, null); // 4. 处理字节码:移除bipush 20和istore_2指令 CodeAttribute codeAttr = newMethod.getMethodInfo().getCodeAttribute(); CodeIterator iterator = codeAttr.iterator(); Bytecode modifiedBytecode = new Bytecode(codeAttr.getConstPool(), codeAttr.getMaxStack(), codeAttr.getMaxLocals()); while (iterator.hasNext()) { int pos = iterator.next(); int opcode = iterator.byteAt(pos); // 跳过bipush 20这个指令,同时跳过后续的istore_2 if (opcode == Opcode.BIPUSH && iterator.byteAt(pos + 1) == 20) { iterator.next(); continue; } // 将其他指令添加到新的字节码对象中 modifiedBytecode.addOpcode(opcode); // 处理带操作数的指令 if (Opcode.length(opcode) > 1) { if (Opcode.isWide(opcode)) { modifiedBytecode.addIndex(iterator.u16bitAt(pos + 1)); } else { modifiedBytecode.add(iterator.byteAt(pos + 1)); } } } // 5. 更新新方法的字节码,并重新计算栈大小和局部变量数 codeAttr.setCode(modifiedBytecode.get()); codeAttr.computeMaxStack(); codeAttr.computeMaxLocals(); // 6. 将新方法添加到类中,然后写入文件 targetClass.addMethod(newMethod); targetClass.writeFile("your/output/directory"); // 替换成你的输出目录 // 释放类池资源 targetClass.detach(); System.out.println("新方法method_new已成功生成!"); } }
代码说明
- 类池初始化:
ClassPool.getDefault()获取默认类池,如果你的类不在默认类路径下,需要用classPool.insertClassPath()添加路径。 - 方法复制:用
CtNewMethod.copy()快速复制原方法的签名和结构,避免手动创建方法的麻烦。 - 字节码遍历修改:通过
CodeIterator遍历原方法的每一条指令,当遇到bipush 20时,跳过当前指令和后续的istore_2,其他指令正常保留。 - 字节码更新:修改后的字节码设置回新方法后,必须调用
computeMaxStack()和computeMaxLocals()重新计算栈和局部变量的最大值,否则会导致字节码验证失败。
另一种更直接的方式(适合已知最终字节码的情况)
如果你已经明确知道修改后的字节码指令,也可以直接手动构造字节码数组并设置:
// 替换步骤4和5的代码 byte[] finalCode = { (byte)Opcode.BIPUSH, 10, // 0: bipush 10 (byte)Opcode.ISTORE_1, // 2: istore_1 (byte)Opcode.GETSTATIC, (byte)(2 >> 8), (byte)(2 & 0xFF), // 3: getstatic #2 (byte)Opcode.ILOAD_1, // 6: iload_1 (byte)Opcode.INVOKEVIRTUAL, (byte)(11 >> 8), (byte)(11 & 0xFF), //7: invokevirtual #11 (byte)Opcode.RETURN //8: return }; codeAttr.setCode(finalCode); codeAttr.computeMaxStack(); codeAttr.computeMaxLocals();
这种方式要注意:常量池的索引(比如#2和#11)必须和原类中的一致,因为这些索引对应原类常量池里的System.out和println(I)V方法引用。
注意事项
- 不要修改JDK核心类(比如
java.lang.String),这类类由Bootstrap类加载器加载,修改后无法正常加载。 - 操作完成后记得调用
detach()释放类池中的CtClass对象,避免内存泄漏。 - 实际项目中建议捕获具体的异常(比如
NotFoundException、CannotCompileException),而不是直接抛出Exception。
内容的提问来源于stack exchange,提问作者adeel iqbal
相关产品推荐
相关产品推荐

