如何使用ASM或Javassist动态修改类的父类指向另一个类
嘿,既然你已经搞定了类的方法和字段修改,那调整父类这个操作其实就是字节码层面的一个小改动,咱们分别用ASM和Javassist来一步步实现,顺便聊聊要踩的坑~
用ASM修改父类
ASM是直接操作字节码指令的工具,类的父类信息其实是在Class文件的头部定义的,对应到ASM的ClassVisitor.visit()方法里的superName参数——咱们只要在这里把原父类的名字替换成目标父类就行。
代码示例
import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.Opcodes; public class ASMSuperClassChanger { // 参数:原类的字节码,目标父类的全限定名(比如com.example.NewParent) public static byte[] changeSuperClass(byte[] originalClassBytes, String targetSuperClass) { // ASM里类名要用斜杠分隔,所以把点替换成斜杠 String asmStyleSuperName = targetSuperClass.replace('.', '/'); ClassReader classReader = new ClassReader(originalClassBytes); // COMPUTE_FRAMES让ASM自动计算栈帧和局部变量表,省得咱们手动处理 ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_FRAMES); ClassVisitor classVisitor = new ClassVisitor(Opcodes.ASM9, classWriter) { @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { // 这里替换父类为目标类 super.visit(version, access, name, signature, asmStyleSuperName, interfaces); } }; classReader.accept(classVisitor, ClassReader.SKIP_DEBUG); return classWriter.toByteArray(); } }
使用说明
- 先获取原类的字节码(比如从文件读取,或者通过类加载器的
getResourceAsStream获取) - 调用上面的方法得到修改后的字节码
- 用自定义类加载器加载修改后的字节码(因为系统类加载器加载过的类不能重复加载)
注意点
- 目标父类必须能被当前类加载器访问到,不然加载修改后的类会抛出
NoClassDefFoundError - 不能修改
final类的父类(Java语法不允许),如果原类是final,得先修改access flags去掉final修饰符 - 要确保新父类的构造方法能被原类的构造方法正确调用(比如原类构造方法调用
super(),新父类必须有无参构造)
用Javassist修改父类
Javassist是更上层的字节码操作工具,API更友好,不用直接跟字节码指令打交道,修改父类只需要调用一个方法就行。
代码示例
import javassist.ClassPool; import javassist.CtClass; import java.lang.reflect.Modifier; public class JavassistSuperClassChanger { // 参数:原类的全限定名,目标父类的全限定名 public static void changeSuperClass(String originalClassName, String targetSuperClassName) throws Exception { ClassPool classPool = ClassPool.getDefault(); CtClass originalClass = classPool.get(originalClassName); CtClass targetSuperClass = classPool.get(targetSuperClassName); // 如果原类是final,先去掉final修饰符(可选,根据需求) if (Modifier.isFinal(originalClass.getModifiers())) { originalClass.setModifiers(originalClass.getModifiers() & ~Modifier.FINAL); } // 直接设置新的父类 originalClass.setSuperclass(targetSuperClass); // 可以选择把修改后的类写入文件,或者直接获取字节码 originalClass.writeFile("./modified-classes"); // 或者 byte[] modifiedBytes = originalClass.toBytecode(); } }
使用说明
- 确保Javassist的依赖已经引入到项目里
- 调用上面的方法,指定原类和目标父类的全限定名
- 修改后的类会被写入指定目录,或者你可以获取字节码后用类加载器加载
注意点
- 如果原类和目标父类有方法签名冲突(比如原类重写了原父类的方法,但新父类的同签名方法是final),会抛出异常
- Javassist默认使用系统的ClassPool,如果你要修改的类在自定义类加载器里,得把类加载器添加到ClassPool中
- 和ASM一样,要处理好构造方法的调用问题,避免运行时抛出
InstantiationError
通用注意事项
- 不管用哪种工具,都不能违反Java的继承规则:比如不能让一个类同时继承多个类,不能修改
java.lang.Object的父类(因为它已经是根类了) - 修改父类后,原类的方法可能会因为方法重写规则改变而出现预期外的行为,最好提前做好测试
- 如果是在运行时修改已经加载的类,要注意类加载器的限制——已经被加载的类不能直接修改,只能通过Instrumentation API在类加载前修改
内容的提问来源于stack exchange,提问作者neuyuandaima
相关产品推荐
相关产品推荐

