ravi k
ravi k

Reputation: 51

Prtining method arguments using byte buddy API

I am working on a project where I need access method arguments during execution. Is it possible to print method arguments using byte buddy framework? any sample code on this using javaagent is highly appreciated.

Upvotes: 0

Views: 736

Answers (2)

Leonis
Leonis

Reputation: 296

Here is an example of how this can be implemented using MethodDelegation. I use it to measure the execution time of methods. I specifically did not begin to remove the extra code, because I want to more fully reveal the capabilities of Byte Buddy.

    package md.leonis.shingler;

    import net.bytebuddy.agent.ByteBuddyAgent;
    import net.bytebuddy.agent.builder.AgentBuilder;
    import net.bytebuddy.implementation.MethodDelegation;
    import net.bytebuddy.implementation.bind.annotation.AllArguments;
    import net.bytebuddy.implementation.bind.annotation.Origin;
    import net.bytebuddy.implementation.bind.annotation.RuntimeType;
    import net.bytebuddy.implementation.bind.annotation.SuperCall;
    import net.bytebuddy.matcher.ElementMatchers;

    import java.lang.instrument.Instrumentation;
    import java.lang.reflect.Method;
    import java.util.Arrays;
    import java.util.concurrent.Callable;
    import java.util.stream.Collectors;

    public class MeasureMethodTest {

        public static void main(String[] args) throws InterruptedException {
            premain(ByteBuddyAgent.install());
            for (int i = 0; i < 4; i++) {
                SampleClass.foo("arg" + i);
            }
        }

        public static void premain(Instrumentation instrumentation) {
            new AgentBuilder.Default()
                    .type(ElementMatchers.nameStartsWith("md.leonis.shingler"))
                    .transform((builder, type, classLoader, module) ->
                            builder.method(ElementMatchers.any()).intercept(MethodDelegation.to(AccessInterceptor.class))
                    ).installOn(instrumentation);
        }

        public static class AccessInterceptor {

            @RuntimeType
            public static Object intercept(@Origin Method method, @SuperCall Callable<?> callable, @AllArguments Object[] args) throws Exception {
                long start = System.nanoTime();
                try {
                    return callable.call();
                } finally {
                    if (method.getAnnotationsByType(Measured.class).length > 0) {
                        String params = Arrays.stream(args).map(Object::toString).collect(Collectors.joining(", "));
                        System.out.println(method.getReturnType().getSimpleName() + " " + method.getName() + "("+ params +") took " + ((System.nanoTime() - start) / 1000000) + " ms");
                    }
                }
            }
        }

        public static class SampleClass {
            @Measured
            static void foo(String s) throws InterruptedException {
                Thread.sleep(50);
            }
        }
    }

This example measures the execution time of all methods found in the md.leonis.shingler package and marked with the @Measured annotation.

To run it, you need two libraries: byte-buddy and byte-buddy-agent.

The result of work:

void foo(arg0) took 95 ms
void foo(arg1) took 50 ms
void foo(arg2) took 50 ms
void foo(arg3) took 50 ms

Note that the console displays the values of all arguments passed to the method. This is the answer to the question asked.

Here is the annotation example:

    package md.leonis.shingler;

    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface Measured {

    }

To be honest, I was not able to directly configure filtering by annotations in the Agent. Here is an example (not working):

    new AgentBuilder.Default()
                    .type(ElementMatchers.isAnnotatedWith(Measured.class))
                    .transform((builder, type, classLoader, module) ->
                            builder.method(ElementMatchers.any()).intercept(MethodDelegation.to(AccessInterceptor.class))
                    ).installOn(instrumentation);

If someone knows how to do this, please comment below.

Upvotes: 0

Rafael Winterhalter
Rafael Winterhalter

Reputation: 44032

Yes, this is possible. You can use MethodDelegation or Advice to inject your code and then use the @AllArguments annotation to get hold of the actual arguments.

The question is, how do you create your code in your project? You can either use a Java agent with the AgentBuilder or create proxy subclasses using ByteBuddy instances. Refer to the documentation and the mentioned classes javadoc to find out how this is done.

Upvotes: 1

Related Questions