Вывод аргументов метода с использованием API-интерфейса для байтов

Я работаю над проектом, где мне нужны аргументы метода доступа во время выполнения. Можно ли напечатать аргументы метода, используя каркас приятеля байтов? Любой пример кода с использованием javaagent высоко ценится.

1 ответ

Решение

Да, это возможно. Ты можешь использовать MethodDelegation или же Advice ввести свой код, а затем использовать @AllArguments аннотация, чтобы получить фактические аргументы.

Вопрос в том, как создать свой код в своем проекте? Вы можете использовать Java-агент с AgentBuilder или создать прокси подклассы, используя ByteBuddy экземпляров. Обратитесь к документации и упомянутым классам javadoc, чтобы узнать, как это делается.

Вот пример того, как это можно реализовать с помощью MethodDelegation. Я использую его для измерения времени выполнения методов. Я специально не стал удалять лишний код, так как хочу более полно раскрыть возможности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);
            }
        }
    }

В этом примере измеряется время выполнения всех методов, найденных в md.leonis.shingler пакет и отмечен @Measured аннотация.

Для его запуска вам понадобятся две библиотеки: byte-buddy и byte-buddy-agent.

Результат работы:

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

Обратите внимание, что консоль отображает значения всех аргументов, переданных методу. Это ответ на заданный вопрос.

Вот пример аннотации:

    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 {

    }

Если честно, напрямую в Агенте настроить фильтрацию по аннотациям не удалось. Вот пример (не работает):

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

Если кто-то знает, как это сделать, прокомментируйте, пожалуйста, ниже.

Другие вопросы по тегам