问题描述
我试图在单独的线程中延迟写入套接字以使用 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
不是 Advice
API 的一部分。
不使用建议似乎会造成严重的限制。确实使用 MethodDelegation 会引发以下错误:
Registration of auxiliary types was disabled: net.bytebuddy.implementation.auxiliary.MethodCallProxy@3206bfce
有没有办法使用 ByteBuddy 的 Advice
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();
}
}
}
解决方法
您需要注册一个 InjectionStrategy
才能将这些必需的类型注入目标类加载器。例如,对于引导加载程序,这可以是一个 UsingInstrumentation
。效率更高,但依靠 JVM 内部 API,您还可以使用 UsingUnsafe
。