如何用ByteBuddy在运行时覆盖Car对象值?测试用例调优求助
Hey there! Let's fix your ByteBuddy issue step by step. I see you're trying to override the Car object returned by Call1.callingCall2() to make your test assertions pass, but you're hitting two frustrating problems: class redefinition schema errors and your AgentBuilder setup not working as expected. Let's break this down and get your test green.
First, let's understand why your original code wasn't working
- Non-static Advice Class: Your
MyAdvicewas a non-static inner class. SincecallingCall2()is a static method, ByteBuddy couldn't instantiate the inner class (it needs an instance of the outerCallingTestclass to do that), so your intercept logic never ran at all. - Missing Static Method Match: You didn't explicitly match the static nature of
callingCall2(), which could lead to unexpected behavior or missed matches. - Unnecessary Class Structure Changes: While you had
disableClassFormatChanges(), the combination of a non-static advice and incorrect method interception might have triggered the schema error you saw.
Here's the fixed test class that will pass your assertions
We'll adjust the advice to be static, explicitly target the static method, and choose between two approaches depending on whether you want to skip the original method logic entirely or modify its returned object.
Option 1: Skip the original method entirely, return our custom Car
This is the simplest approach if you don't need any logic from callingCall2() or callMethod2() to run:
package com.example; import net.bytebuddy.agent.ByteBuddyAgent; import net.bytebuddy.agent.builder.AgentBuilder; import net.bytebuddy.agent.builder.AgentBuilder.RedefinitionStrategy; import net.bytebuddy.asm.Advice; import net.bytebuddy.dynamic.DynamicType; import net.bytebuddy.matcher.ElementMatchers; import net.bytebuddy.utility.JavaModule; import org.junit.Test; import java.lang.instrument.Instrumentation; import static net.bytebuddy.matcher.ElementMatchers.*; import static org.junit.Assert.assertEquals; public class CallingTest { @Test public void givenCall1_whenRedefined_thenReturnFooRedefined() throws Exception { premain(null, ByteBuddyAgent.install()); Car result = Call1.callingCall2(); // Use static method call directly (more accurate) assertEquals("2011", result.getModel()); assertEquals("Maruti", result.getName()); } public static void premain(String arguments, Instrumentation instrumentation) { new AgentBuilder.Default() .disableClassFormatChanges() // Prevents schema modification errors .with(RedefinitionStrategy.RETRANSFORMATION) .type(is(Call1.class)) .transform((builder, typeDescription, classLoader, module) -> builder.method(named("callingCall2").and(isStatic())) // Explicitly match static method .intercept(Advice.to(MyStaticAdvice.class))) .installOn(instrumentation); } // Must be static to work with static method interception public static class MyStaticAdvice { // Skip original method execution, return our custom Car directly @Advice.OnMethodEnter(skipOnExit = true) public static Car overrideReturn() { Car carTest = new Car(); carTest.setModel("2011"); carTest.setName("Maruti"); return carTest; } } }
Option 2: Run the original method, then modify its returned Car
If you need the original callingCall2() logic to execute but want to tweak the resulting Car object, use this approach instead:
// Replace the MyStaticAdvice class in the above code with this: public static class MyStaticAdvice { // Modify the original method's returned Car object @Advice.OnMethodExit public static void modifyReturn(@Advice.Return(readOnly = false) Car car) { car.setModel("2011"); car.setName("Maruti"); } }
Key fixes explained
- Static Advice Class: By making
MyStaticAdvicestatic, ByteBuddy can access it without needing an instance ofCallingTest, which is required for intercepting static methods. - Explicit Static Method Match: Adding
.and(isStatic())ensures we only target the staticcallingCall2()method, avoiding accidental matches with non-static methods of the same name. - Skip Original Execution (Option 1): The
skipOnExit = trueflag tells ByteBuddy to skip running the originalcallingCall2()code entirely, replacing it with our custom return value. - Modify Return Value (Option 2): Using
@Advice.Return(readOnly = false)lets us modify the object returned by the original method, perfect for when you need to retain the original logic but adjust the result.
内容的提问来源于stack exchange,提问作者jitin rathi

