ByteBuddy动态子类组合拦截器实现指定方法执行顺序遇栈校验错误
问题根本原因
Implementation.Compound的字节码拼接逻辑非常直白:它会按传入顺序把两个实现生成的字节码直接串联成完整方法体,既不会检测每个实现是否自带方法返回指令,也不会自动匹配前后的操作数栈状态。- 你当前组合顺序中的
InvocationHandlerAdapter.of(decoration),默认会在生成的字节码中执行完装饰器的invoke逻辑后,直接把结果压入操作数栈并插入自动返回指令完成方法返回——这一点从报错附带的字节码可直接验证:偏移0x1e位置的b0就是areturn指令,执行到这里方法就已经退出了。 - 这种硬拼接会把
SuperMethodCall.INSTANCE生成的字节码挤在areturn指令之后,成为永远不会被执行的死代码。JDK 11的类校验器在扫描时,会发现areturn指令后的操作数栈没有合法的栈帧定义,最终抛出Expecting a stack map frame错误。 - 你之前单独使用两个实现、以及链式调用两个实现时都能正常运行,本质是因为这些场景下生成的方法体字节码结构是连续合法的,没有出现提前返回后接死代码的问题。其中
SuperMethodCall.INSTANCE.andThen(InvocationHandlerAdapter.of(decoration))可正常执行,是因为SuperMethodCall实现了Implementation.Composable接口,它在拼接后续逻辑时,会主动把前序逻辑产生的栈值暂存到局部变量中,等所有拼接逻辑执行完再统一插入返回指令,不会破坏字节码结构。
可实现预期逻辑的调整方案
要达成“先触发装饰器invoke逻辑,再调用父类原方法”的执行顺序,不能直接用朴素的Implementation.Compound拼接两个自带返回逻辑的实现,可根据需求选择以下两种可靠方式:
方案1:关闭InvocationHandlerAdapter的自动返回(改动最小)
InvocationHandlerAdapter提供了withoutReturning()配置,调用后它会在生成的字节码中,执行完装饰器逻辑后主动把handler的返回值从操作数栈弹出,不会提前插入返回指令,保证后续拼接的字节码栈状态合法。调整后的代码如下:
Class<? extends Sample> dynamicSubclass = new ByteBuddy() .subclass(Sample.class) .method(ElementMatchers.named("someMethod")) .intercept(new Implementation.Compound( // 关闭自动返回,调用完装饰器后不退出方法 InvocationHandlerAdapter.of(decoration).withoutReturning(), SuperMethodCall.INSTANCE )) .make() .load(Sample.class.getClassLoader()) .getLoaded();
方案2:使用Advice做顺序控制(灵活性更高)
Advice是ByteBuddy提供的无栈帧冲突的字节码增强方案,可以精准定义方法不同阶段的执行逻辑,不会出现硬拼接导致的结构问题。示例代码:
// 定义方法进入阶段的增强逻辑 class DecorationAdvice { @Advice.OnMethodEnter static void onEnter(@Advice.This Object instance, @Advice.Origin Method originMethod, @Advice.AllArguments Object[] methodArgs, // 绑定装饰器实例 @Advice.FieldValue("decorationHandler") InvocationHandler decoration) throws Throwable { decoration.invoke(instance, originMethod, methodArgs); } } Class<? extends Sample> dynamicSubclass = new ByteBuddy() .subclass(Sample.class) .method(ElementMatchers.named("someMethod")) // 以父类方法调用为方法主体 .intercept(SuperMethodCall.INSTANCE) // 绑定进入阶段的装饰逻辑 .advice(DecorationAdvice.class) .make() .load(Sample.class.getClassLoader()) .getLoaded();
内容的提问来源于stack exchange,提问作者morgwai
相关产品推荐
相关产品推荐

