Reputation: 51113
Using ByteBuddy, can I implement one instance method by calling another and transforming the result?
For instance (toy example):
public abstract class Foo {
public String bar() {
return "bar";
}
public abstract int baz();
}
Given the above, can I implement baz
such that it calls bar()
and returns the length of the returned string? I.e., as if it were:
public int baz() {
return bar().length();
}
Naively, I tried the following:
Method bar = Foo.class.getDeclaredMethod("bar");
Method baz = Foo.class.getDeclaredMethod("baz");
Method length = String.class.getDeclaredMethod("length");
Foo foo = new ByteBuddy()
.subclass(Foo.class)
.method(ElementMatchers.is(baz))
.intercept(
MethodCall.invoke(bar) // call bar()...
.andThen(MethodCall.invoke(length)) // ... .length()?
).make()
.load(Foo.class.getClassLoader())
.getLoaded()
.newInstance();
System.out.println(foo.baz());
However, it looks like I was wrong in thinking andThen()
is invoked on the return value of the first invocation; it looks like it's invoked on the generated instance.
Exception in thread "main" java.lang.IllegalStateException:
Cannot invoke public int java.lang.String.length() on class Foo$ByteBuddy$sVgjXXp9
at net.bytebuddy.implementation.MethodCall$MethodInvoker$ForContextualInvocation
.invoke(MethodCall.java:1667)
I also tried an interceptor:
class BazInterceptor {
public static int barLength(@This Foo foo) {
String bar = foo.bar();
return bar.length();
}
}
with:
Foo foo = new ByteBuddy()
.subclass(Foo.class)
.method(ElementMatchers.is(baz))
.intercept(MethodDelegation.to(new BazInterceptor()))
// ...etc.
This ran, but produced the nonsensical result 870698190
, and setting breakpoints and/or adding print statements in barLength()
suggested it's never getting called; so clearly I'm not understanding interceptors or @This
properly, either.
How can I get ByteBuddy to invoke one method and then invoke another on its return value?
Per k5_'s answer: BazInterceptor
works if either:
new BazInterceptor()
, as above, but make barLength()
an instance method, or:barLength()
a class method, but delegate to BazInterceptor.class
instead of to an instance.I suspect the 870698190
was delegating to hashCode()
of the BazInterceptor
instance, though I didn't actually check.
Upvotes: 3
Views: 1572
Reputation: 44032
There is not currently a good way in Byte Buddy but this would be an easy feature to add. You can track the progress on GitHub. I will add it once I find some time.
If you want to implement such chained calls today, you can implement them in Java code and inline this code using the Advice
component. Alternatively, you can write the byte code more explicitly by creating your own ByteCodeAppender
based on MethodInvocation
instances where you have to load the arguments manually however.
Upvotes: 2
Reputation: 5558
You use an instance as interceptor, that means instance methods are prefered (maybe static method are not accepted at all). There is an instance method that matches the signature of your int baz()
method, it is int hashCode()
. The number you are getting is the hashcode of the new BazInterceptor()
instance.
Options i am aware of:
static
from barLength
that way it will actually be used for interception..intercept(MethodDelegation.to(BazInterceptor.class))
I would prefer the second option as you are not using any fields/state of the BazInterceptor
instance.
Upvotes: 1