首页 > 解决方案 > 是否可以使用 ByteBuddy Advice 机制将本机方法封装在线程中?

问题描述

我试图在单独的线程中延迟写入套接字,以使用 javaagent 模拟网络堆栈中的延迟。使用 ByteBuddy,我设法装饰了原生java.net.SocketOutputStream::writeSocket0. 但是,这样做,我暂停了当前线程,这不是我想要实现的。为了避免这个问题,我想创建一个可以调用本机方法的线程。不幸的是,我对 ByteBuddy 的了解仍然相当有限,我没有设法找到实现这一目标的方法。我的最佳尝试如下所示:

//premain function
public static void premain(final String agentArgs, final Instrumentation instrumentation) {
    try{
        BootLoaderInjector.inject(DelayedExecutor.class);
        long duration = 100;

        new AgentBuilder.Default()
            .with(new ByteBuddy().with(Implementation.Context.Disabled.Factory.INSTANCE))
            .with(AgentBuilder.InitializationStrategy.NoOp.INSTANCE)
            .with(AgentBuilder.TypeStrategy.Default.REBASE)
            .enableNativeMethodPrefix("somePrefix")
            .with(AgentBuilder.Listener.StreamWriting.toSystemError().withErrorsOnly())
            .ignore(nameStartsWith("net.bytebuddy."))
            .type(named("java.net.SocketOutputStream"))
            .transform((transformer, typeDescription, classLoader, javaModule) ->
                transformer.method(named("socketWrite0"))
                    .intercept(
                        Advice.withCustomMapping().bind(Duration.class, duration)
                        .to(DelayInterceptor.class)
                )
            )
            .installOn(instrumentation);
    }
    catch (Throwable t){
        System.err.printf(t.getMessage());
    }
}

// interceptor class
public class DelayInterceptor {
    @Advice.OnMethodEnter
    public static void intercept(@SuperCall Callable<?> caller, @Duration long duration) {
        DelayedExecutor.execute(callable, duration);
    }
}

// execute the intercepted method in a new thread with a delay
public class DelayedExecutor implements Runnable {
    private final Callable<?> callable;
    private final long delay;

    private DelayedExecutor(Callable<?> callable, long delay) {
        this.callable = callable;
        this.delay = delay;
    }

    public static void execute(Callable<?> callable, long delay){
        new Thread(new DelayedExecutor(callable, delay)).start();
    }

    @Override
    public void run() {
        try {
            Thread.sleep(this.delay);
            this.callable.call();
        } catch (Exception ignore) {}
    }
}

但是,这会失败,因为@Callable它不是AdviceAPI 的一部分。

不使用建议似乎会造成严重的限制。实际上使用 MethodDelegation 会引发以下错误:

Registration of auxiliary types was disabled: net.bytebuddy.implementation.auxiliary.MethodCallProxy@3206bfce

有没有办法使用AdviceByteBuddy 的 API 将函数调用包装在块内?

编辑

为了增加清晰度并解决答案建议,这里是它的实现BootLoaderInjector.inject,显示了如何将类型添加到依赖于引导加载程序的类加载器中ClassInjector.UsingUnsafe.ofBootLoader()

public class BootLoaderInjector {
    public static void inject(Class<?> targetClass) {
        try {
            ClassInjector.UsingUnsafe.ofBootLoader().inject(singletonMap(
                    new TypeDescription.ForLoadedType(targetClass),
                    ClassFileLocator.ForClassLoader.read(targetClass)
            ));
        } catch (Throwable e){
            System.out.println("Something went terribly wrong: " + e.getMessage());
            e.printStackTrace();
        }
    }
}

标签: javabyte-buddyjavaagents

解决方案


您需要注册一个InjectionStrategy以将这些所需的类型注入目标类加载器。例如,对于引导加载程序,这可能是一个UsingInstrumentation. 更高效,但依赖 JVM 内部 API,您还可以使用UsingUnsafe.


推荐阅读