字节预算转换多次运行一次执行

问题描述

我写了一个javaagent,以捕获Apache execute的{​​{1}}方法的执行时间。它正在捕获时间,但是运行了三遍。

org.apache.http.client.HttpClient

我正在使用import java.lang.instrument.Instrumentation; import net.bytebuddy.agent.builder.AgentBuilder; import net.bytebuddy.implementation.MethodDelegation; import static net.bytebuddy.matcher.ElementMatchers.isMethod; import static net.bytebuddy.matcher.ElementMatchers.isAbstract; import static net.bytebuddy.matcher.ElementMatchers.takesArguments; import static net.bytebuddy.matcher.ElementMatchers.takesArgument; import static net.bytebuddy.matcher.ElementMatchers.named; import static net.bytebuddy.matcher.ElementMatchers.not; public class TimerAgent { public static void premain( String arguments,Instrumentation instrumentation ) { new AgentBuilder.Default() .type( implementsInterface(named("org.apache.http.client.HttpClient")) ) .transform((builder,type,classLoader,module) -> builder.method(isMethod() .and(named("execute")) .and(not(isAbstract())) .and(takesArguments(3)) .and(takesArgument(0,named("org.apache.http.client.methods.HttpUriRequest"))) .and(takesArgument(1,named("org.apache.http.client.ResponseHandler"))) .and(takesArgument(2,named("org.apache.http.protocol.HttpContext")))) .intercept(MethodDelegation .to(TimingInterceptor.class)) ).installOn(instrumentation); } } import java.lang.reflect.Method; import java.util.concurrent.Callable; import net.bytebuddy.implementation.bind.annotation.Origin; import net.bytebuddy.implementation.bind.annotation.RuntimeType; import net.bytebuddy.implementation.bind.annotation.SuperCall; public class TimingInterceptor { @RuntimeType() public static Object intercept( @Origin Method method,@SuperCall Callable<?> callable ) { long start = System.currentTimeMillis(); try { try { return callable.call(); } catch (Exception e) { e.printstacktrace(); } } finally { System.out.println( "Took " + (System.currentTimeMillis() - start)); } return 0; } } 发出HTTP请求。 客户代码

DefaultHttpClient

控制台输出

import java.io.IOException;

import org.apache.http.httpentity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.impl.client.DefaultHttpClient;

public class Main {

    public static void main(String[] args) throws IOException {

        HttpClient client = new DefaultHttpClient();

        HttpUriRequest httpUriRequest = new HttpGet("http://www.google.com");

        HttpResponse response = client
            .execute(httpUriRequest,new ResponseHandler<HttpResponse>() {
                public HttpResponse handleResponse(final HttpResponse response)
                    throws ClientProtocolException,IOException {
                    return response;
                }
            });
    }
}

Maven依赖项:

Took 512
Took 512
Took 512

This<dependency> <groupId>net.bytebuddy</groupId> <artifactId>byte-buddy</artifactId> <version>1.10.10</version> </dependency> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpasyncclient</artifactId> <version>4.1.4</version> <scope>provided</scope> </dependency>

的实现

这是我执行应用程序的方式:

implementsInterface

我不确定是什么原因导致它打印3次。

更新: 感谢@kriegaex提供MCVE,可以在this GitHub repository中找到它。

解决方法

首先,您从哪里获得implementsInterface元素匹配器?它不是ByteBuddy的一部分,至少在当前版本中不是。我用isSubTypeOf代替了它,以使代码得以编译。无论如何,如果您这样激活日志记录

new AgentBuilder.Default()
.with(AgentBuilder.Listener.StreamWriting.toSystemError().withTransformationsOnly())
  .with(AgentBuilder.InstallationListener.StreamWriting.toSystemError())
  .type(isSubTypeOf(HttpClient.class))
  // ...

您将在控制台上看到以下内容:

[Byte Buddy] BEFORE_INSTALL net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer$ByteBuddy$ModuleSupport@1eb5174b on sun.instrument.InstrumentationImpl@67080771
[Byte Buddy] INSTALL net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer$ByteBuddy$ModuleSupport@1eb5174b on sun.instrument.InstrumentationImpl@67080771
[Byte Buddy] TRANSFORM org.apache.http.impl.client.DefaultHttpClient [jdk.internal.loader.ClassLoaders$AppClassLoader@2626b418,unnamed module @4d518b32,loaded=false]
[Byte Buddy] TRANSFORM org.apache.http.impl.client.AbstractHttpClient [jdk.internal.loader.ClassLoaders$AppClassLoader@2626b418,loaded=false]
[Byte Buddy] TRANSFORM org.apache.http.impl.client.CloseableHttpClient [jdk.internal.loader.ClassLoaders$AppClassLoader@2626b418,loaded=false]
Handling response: HTTP/1.1 200 OK [Date: Mon,26 Oct 2020 04:46:18 GMT,Expires: -1,Cache-Control: private,max-age=0,Content-Type: text/html; charset=ISO-8859-1,P3P: CP="This is not a P3P policy! See g.co/p3phelp for more info.",Server: gws,X-XSS-Protection: 0,X-Frame-Options: SAMEORIGIN,Set-Cookie: 1P_JAR=2020-10-26-04; expires=Wed,25-Nov-2020 04:46:18 GMT; path=/; domain=.google.com; Secure,Set-Cookie: NID=204=qecfPmmSAIwXyTGneon07twIXIIw4EyPiArm67OK5nHyUowAq3_8QJZ7gw9k8Nz5ZuGuHoyadCK-nsAKGkZ8TGaD5mdPlAXeVenWwzQrmFkNNDiEpxCj-naf4V6SKDhDUgA18I-2z36ornlEUN7xinrHwWfR0pc4lvlAUx3ssJk; expires=Tue,27-Apr-2021 04:46:18 GMT; path=/; domain=.google.com; HttpOnly,Accept-Ranges: none,Vary: Accept-Encoding,Transfer-Encoding: chunked] org.apache.http.conn.BasicManagedEntity@6928f576
Took 1870
Took 1871
Took 1871

看到了吗?您已经在整个类层次结构中安装了拦截器,总共安装了三个类:

HttpClient class hierarchy

因此,您要么需要限制元素匹配器,要么必须使用多个日志行。

免责声明:我不是ByteBuddy专家,也许拉斐尔·温特豪德(Rafael Winterhalter)稍后会写出更好的答案,也许我的解决办法不规范。

即使技术上仅在CloseableHttpClient中定义了该方法,在AbstractHttpClientDefaultHttpClient中也没有覆盖该方法,但似乎拦截器与所有类都匹配。限制类型匹配的一种方法是检查目标类是否实际上包含您要检测的方法:

ElementMatcher.Junction<MethodDescription> executeMethodDecription = isMethod()
  .and(named("execute"))
  .and(not(isAbstract()))
  .and(takesArguments(3))
  .and(takesArgument(0,named("org.apache.http.client.methods.HttpUriRequest")))
  .and(takesArgument(1,named("org.apache.http.client.ResponseHandler")))
  .and(takesArgument(2,named("org.apache.http.protocol.HttpContext")));

new AgentBuilder.Default()
  .with(AgentBuilder.Listener.StreamWriting.toSystemError().withTransformationsOnly())
  .with(AgentBuilder.InstallationListener.StreamWriting.toSystemError())
  .type(
    isSubTypeOf(HttpClient.class)
      .and(declaresMethod(executeMethodDecription))
  )
  .transform((builder,type,classLoader,module) -> builder
    .method(executeMethodDecription)
    .intercept(MethodDelegation.to(TimingInterceptor.class))
  )
  .installOn(instrumentation);

现在控制台日志应变为:

[Byte Buddy] BEFORE_INSTALL net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer$ByteBuddy$ModuleSupport@67080771 on sun.instrument.InstrumentationImpl@72cde7cc
[Byte Buddy] INSTALL net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer$ByteBuddy$ModuleSupport@67080771 on sun.instrument.InstrumentationImpl@72cde7cc
[Byte Buddy] TRANSFORM org.apache.http.impl.client.CloseableHttpClient [jdk.internal.loader.ClassLoaders$AppClassLoader@2626b418,unnamed module @6ea2bc93,26 Oct 2020 05:21:25 GMT,Set-Cookie: 1P_JAR=2020-10-26-05; expires=Wed,25-Nov-2020 05:21:25 GMT; path=/; domain=.google.com; Secure,Set-Cookie: NID=204=N7U6SBBW5jjM1hynbowChQ2TyMSbiLHHwioKYusPVHzJPkiRaSvpmeIlHipo34BAq5QqlJnD7GDD1iv6GhIZlEEl7k3MclOxNY9WGn9c6elHikj6MPUhXsAapYz9pOVFl_DjAInWv5pI00FfUZ6i5mK14kq3JIXu-AV84WKDxdc; expires=Tue,27-Apr-2021 05:21:25 GMT; path=/; domain=.google.com; HttpOnly,Transfer-Encoding: chunked] org.apache.http.conn.BasicManagedEntity@5471388b
Took 1274

更新:实际上,intercept()的行为在其javadoc中进行了描述:

通过提供的实现来实现先前定义或匹配的方法。方法拦截通常通过以下方式之一实现:

  1. 如果通过检测类型声明了一个方法,并且类型生成器创建了一个子类或重新定义,则任何先前存在的方法都将被给定的实现替换。任何先前定义的实现都将丢失。
  2. 如果一种方法是由检测类型声明的,并且类型构建器创建了检测类型的重新建立版本,则原始方法将保留在检测类型内的私有合成方法中。因此,原始方法保持可调用性,并被视为新方法的直接超级方法。重新定义类型时,因此可以在替换预先存在的方法主体时调用非虚拟方法的super方法。
  3. 如果虚拟方法是从超类型继承的,则它将被覆盖。覆盖的方法可用于超级方法调用。

更新2:我的完整MCVE可在this GitHub repository中使用,以方便所有人。