如何使用Byte-Buddy自定义Implementation重定义第三方类方法?
Byte-Buddy自定义Implementation实现方法参数修改
需求场景
现有第三方Demo类:
class Demo{ public void handle(String value) { // 原有业务逻辑 } }
需要在handle方法执行时,自动将输入参数value转换为小写,最终实现逻辑等价于:
class Demo{ public void handle(String value) { value = value.toLowerCase(); // 原有业务逻辑 } }
由于需要重定义类,无法使用Byte-Buddy的MethodDelegation,需通过自定义Implementation直接编写字节码实现。
已有代码框架
DynamicType.Loaded<SumExample> loaded = new ByteBuddy() .subclass(Demo.class) .method(named("handle")) .intercept(MyImplementation.INSTANCE) .make() .load(Thread.currentThread().getContextClassLoader(), ClassLoadingStrategy.Default.WRAPPER); // 自定义Implementation实现类 enum MyImplementation implements Implementation { INSTANCE; // 单例实例 @Override public InstrumentedType prepare(InstrumentedType instrumentedType) { return instrumentedType; } @Override public ByteCodeAppender appender(Target implementationTarget) { return MyAppender.INSTANCE; } } // 需要完善的ByteCodeAppender实现 enum MyAppender implements ByteCodeAppender { INSTANCE; // 单例实例 @Override public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext, MethodDescription instrumentedMethod) { methodVisitor.visitCode(); // 待填充字节码逻辑 methodVisitor.visitEnd(); return new Size(0, instrumentedMethod.getStackSize()); } }
完善后的MyAppender实现
下面是填充后的字节码编写逻辑,包含参数小写转换、父类方法调用,还可按需添加非空检查:
import org.objectweb.asm.Opcodes; import org.objectweb.asm.Label; // ... @Override public Size apply(MethodVisitor methodVisitor, Implementation.Context implementationContext, MethodDescription instrumentedMethod) { methodVisitor.visitCode(); // 可选:添加参数非空检查 Label notNullLabel = new Label(); methodVisitor.visitVarInsn(Opcodes.ALOAD, 1); // 加载参数value到栈顶 methodVisitor.visitJumpInsn(Opcodes.IFNONNULL, notNullLabel); // 若value为null,抛出NullPointerException methodVisitor.visitTypeInsn(Opcodes.NEW, "java/lang/NullPointerException"); methodVisitor.visitInsn(Opcodes.DUP); methodVisitor.visitLdcInsn("Parameter 'value' cannot be null"); methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/NullPointerException", "<init>", "(Ljava/lang/String;)V", false); methodVisitor.visitInsn(Opcodes.ATHROW); methodVisitor.visitLabel(notNullLabel); // 核心逻辑:将value转换为小写并更新参数 methodVisitor.visitVarInsn(Opcodes.ALOAD, 1); // 加载原value参数 // 调用String.toLowerCase()方法 methodVisitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "toLowerCase", "()Ljava/lang/String;", false); methodVisitor.visitVarInsn(Opcodes.ASTORE, 1); // 将小写结果存回value参数 // 调用父类Demo的handle方法,执行原有业务逻辑 methodVisitor.visitVarInsn(Opcodes.ALOAD, 0); // 加载当前实例this methodVisitor.visitVarInsn(Opcodes.ALOAD, 1); // 加载更新后的value参数 // 注意:替换"com/example/Demo"为Demo类的实际全限定类名(用/分隔包路径) methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, "com/example/Demo", "handle", "(Ljava/lang/String;)V", false); // 方法返回 methodVisitor.visitInsn(Opcodes.RETURN); methodVisitor.visitEnd(); // 栈最大深度为2(调用父类方法时需同时容纳this和value两个引用),局部变量数沿用原方法配置 return new Size(2, instrumentedMethod.getStackSize()); }
代码说明
- 非空检查(可选):通过字节码判断
value是否为null,若为null则抛出异常,避免后续调用toLowerCase()时出现空指针。 - 参数小写转换:
ALOAD 1:将局部变量表中索引1的value参数加载到操作数栈顶(实例方法中,索引0为this,第一个参数从索引1开始)。INVOKEVIRTUAL:调用String.toLowerCase()方法,栈顶变为转换后的小写字符串。ASTORE 1:将小写结果存回局部变量表的索引1位置,完成value = value.toLowerCase()的赋值操作。
- 调用父类方法:通过
INVOKESPECIAL调用父类Demo的handle方法,确保原有业务逻辑被执行。 - 栈大小配置:设置栈最大深度为2,因为调用父类方法时,操作数栈需要同时容纳
this和value两个对象引用。
内容的提问来源于stack exchange,提问作者Ning LI
相关产品推荐
相关产品推荐

