Вывод аргументов метода с использованием 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);
Если кто-то знает, как это сделать, прокомментируйте, пожалуйста, ниже.