I'm trying to change the return value of a method of an already loaded class.
From ByteBuddy's documentation (http://bytebuddy.net/#/tutorial) this seems possible using the Java agent, as long as I don't add any field/method.
My code is the following:
ByteBuddyAgent.install();
new ByteBuddy()
.redefine(StuffImpl.class)
.method(returns(Result.class))
.intercept(FixedValue.value(new Result("intercepted")))
.make()
.load(StuffImpl.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
But I'm getting the following exception:
java.lang.UnsupportedOperationException: class redefinition failed: attempted to change the schema (add/remove fields)
The thing is, I'm not adding any method. Where does Byte Buddy add a field or method in the above code?
EDIT:
public class StuffImpl {
public Result validate() {
return new Result("original");
}
}
public class Result {
private String result;
public Result(String result) {
this.result = result;
}
}
You define a delegation to a fixed value new Result("intercepted")
that Byte Buddy needs to store somewhere. The FixedValue
implementation creates a static field for you such that the generated method can read your value from it. You can work arround this in different ways by avoiding FixedValue
, for example:
Delegate to another class that holds the value in a field (retains reference identity).
MethodDelegation.to(Holder.class);
class Holder {
static Result result = new Result("intercepted");
static Result intercept() { return result; }
}
This is the most universal approach, you can of course return new Result("intercepted")
directly from the method.
Create the instance dynamically:
MethodCall.construct(Result.class.getDeclaredConstructor(String.class))
.with("intercepted");
In this case, the "intercepted"
string does not need to be stored in a field because it can be referenced in the class's constant pool (the same goes for primitive values).
Your StuffImpl
probably defines a static initializer. This initializer is factored out by Byte Buddy into a private
method such that it can add additional statements to it.
You can disable this behaviour by setting:
new ByteBuddy().with(Implementation.Context.Disabled.Factory.INSTANCE);
This should really be in the docs and I will add it for the next release.