Situation detailed below with some pseudocode of sorts.
Class implements Interface {
method() {}
}
and
@Advice.OnMethodEnter()
public static methodEnter(@Advice.Argument(0) final Interface) {
System.out.println("I don't end up seeing this print message when calling methods from Class")
}
Does anyone know why this might be? Mostly looking for a general "this should work" versus "this does not work and I am missing something critical about how ByteBuddy works". I'm looking at some open-source code that seems like a Black Box, that isn't operating as expected, so I'm trying to get a grasp of how AOP works. Learning resources are also appreciated, albeit ByteBuddy seems a bit particular so I haven't been able to find answers from their documentation so far.
If you want to learn AOP basics, I suggest you use AspectJ, it is easier to learn and apply than a relatively low-level byte code instrumentation framework like ByteBuddy (BB).
The thing with BB is that you need to set up the instrumentation first, you cannot just write an aspect, compile the code and expect it to work. In order to do that you need to know a few basics about Java instrumentation and, depending on your scenario, also on Java agents. For example, if you want to transform a class which was already loaded, you can only transform it in a way which avoids introducing new methods or fields or changing existing ones with regard to their signature and modifiers. If you add both byte-buddy
and byte-buddy-agent
to your classpath, you might want to do something like this:
package de.scrum_master.stackoverflow.q62521099;
import net.bytebuddy.agent.ByteBuddyAgent;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.asm.Advice;
import java.lang.instrument.Instrumentation;
import java.lang.reflect.Method;
import java.util.Arrays;
import static net.bytebuddy.agent.builder.AgentBuilder.RedefinitionStrategy.RETRANSFORMATION;
import static net.bytebuddy.implementation.bytecode.assign.Assigner.Typing.DYNAMIC;
import static net.bytebuddy.matcher.ElementMatchers.*;
class ByteBuddyDemo {
public static void main(String[] args) {
weaveAspect();
MyInterface myInterface = new MyClass();
myInterface.doSomething();
System.out.println("7 + 8 = " + myInterface.add(7, 8));
}
private static void weaveAspect() {
Instrumentation instrumentation = ByteBuddyAgent.install();
new AgentBuilder.Default()
.disableClassFormatChanges()
.with(RETRANSFORMATION)
.type(isSubTypeOf(MyInterface.class))
.transform((builder, typeDescription, classLoader, module) ->
builder.visit(
Advice.to(MyByteBuddyAspect.class).on(isMethod())
)
)
.installOn(instrumentation);
}
interface MyInterface {
void doSomething();
int add(int a, int b);
}
static class MyClass implements MyInterface {
@Override
public void doSomething() {
System.out.println("Doing something");
}
@Override
public int add(int a, int b) {
return a + b;
}
}
static abstract class MyByteBuddyAspect {
@Advice.OnMethodEnter
public static void before(
@Advice.This(typing = DYNAMIC, optional = true) MyInterface target,
@Advice.Origin Method method,
@Advice.AllArguments(readOnly = false, typing = DYNAMIC) Object[] args
)
{
System.out.println("MyByteBuddyAspect.before");
System.out.println(" Method: " + method);
System.out.println(" Instance: " + target);
System.out.println(" Arguments: " + Arrays.deepToString(args));
}
}
}
See? There is quite a lot of boilerplate code here. I do not want to explain all the details, better search the Web for tutorials. The BB web site also has one, even though outdated in some regards. BB is so feature-rich, it can be quite overwhelming.
The console log for my solution above would be:
MyByteBuddyAspect.before
Method: public void de.scrum_master.stackoverflow.q62521099.ByteBuddyDemo$MyClass.doSomething()
Instance: de.scrum_master.stackoverflow.q62521099.ByteBuddyDemo$MyClass@61544ae6
Arguments: []
Doing something
MyByteBuddyAspect.before
Method: public int de.scrum_master.stackoverflow.q62521099.ByteBuddyDemo$MyClass.add(int,int)
Instance: de.scrum_master.stackoverflow.q62521099.ByteBuddyDemo$MyClass@61544ae6
Arguments: [7, 8]
7 + 8 = 15
So there you see how you can access the target instance, the method information (only use if needed, otherwise omit it) and the arguments list.
You can add more options if you want to use a combination of enter/exit advices in order to dynamically decide whether to execute or skip the original method, whether to manipulate the arguments or the result etc. All of this is quite a learning curve, but I recently also went through it. So it is definitely possible. I still much prefer AspectJ for AOP and use BB only for more low-level stuff like creating special types of test doubles (mocks). But that is another story.