I have a Java class Fns with some public instance methods annotated with @Watch:
class Fns {
@Watch
int add(int x, int y) {
}
}
Every time an instance method is called on an instance of Fns, e.g. fns.add(1, 2), I want to intercept that call with a method public static Object intercept(Object receiver, Method receiverMethod, Object[] args).
I have tried doing this in ByteBuddy, but most of the methods I tried rely on subclassing Fns, which then requires the subclass to call the superclass constructor (and the constructor and fields of Fns could take any form).
I just need a proxy class with the same methods – it doesn’t have to have the same constructor or fields. The intercept method needs to be able to call the original method (e.g. fns.add(1, 2) once it has done some work to log the method call.
You’re trying to intercept annotated instance method calls (@Watch) on a class Fns using ByteBuddy, without needing to subclass Fns or deal with its constructor or internal fields. You want to:
Intercept methods annotated with @Watch
Log or handle them via a static intercept(...) method
Still call the original method afterward
Avoid the complexities of subclassing Fns
Solution
Use ByteBuddy’s redefinition/decoration capabilities with method delegation, combined with an @RuntimeType interceptor.
You don’t need to subclass – you can redefineFns at runtime and instrument its methods directly.
Step-by-step
Define the annotation:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Watch {}
Create the interceptor class:
public class WatchInterceptor {
@RuntimeType
public static Object intercept(
@This Object receiver,
@Origin Method method,
@AllArguments Object[] args,
@SuperCall Callable<Object> originalMethod
) throws Exception {
// Log or inspect here
System.out.println("Intercepted: " + method.getName());
// Optionally log args, receiver, etc.
// Call the original method
return originalMethod.call();
}
}
Redefine the Fns class using ByteBuddy:
new ByteBuddy()
.redefine(Fns.class) // <-- Redefines the actual class, not a subclass
.method(isAnnotatedWith(Watch.class)) // Target only methods with @Watch
.intercept(MethodDelegation.to(WatchInterceptor.class))
.make()
.load(Fns.class.getClassLoader(), ClassReloadingStrategy.fromInstalledAgent());
You must have a Java agent installed for this to work with class redefinition. If you don’t, install the ByteBuddy agent:
ByteBuddyAgent.install();
Usage:
Fns fns = new Fns();
fns.add(1, 2); // This will now be intercepted
Notes
This approach modifies the original Fns class at runtime, so you don’t need to create a proxy or deal with constructors.
You’re delegating only the @Watch-annotated methods to your interceptor.
The @SuperCall Callable<Object> originalMethod gives you a handle to call the actual original method after interception.
Summary
Use redefine() not subclass()
Target methods with @Watch using isAnnotatedWith(...)
Delegate to a static interceptor with MethodDelegation.to(...)
Use @SuperCall to invoke the original method
Install the ByteBuddy agent if you haven’t
Let me know if you need help wiring this up in a Spring or JUnit context.