有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

一次执行多次运行的java Bytebuddy转换

我编写了一个javaagent,如下所示,以捕获apache org.apache.http.client.HttpClientexecute方法的执行时间。它正在捕获时间,但它运行了三次

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;
    }
}

我正在使用DefaultHttpClient发出HTTP请求。 客户端代码:

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;
                }
            });
    }
}

控制台输出:

Took 512
Took 512
Took 512

Maven依赖项:

<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>

ThisimplementsInterface的实现

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

java -javaagent:"javaagent.jar" -jar application.jar

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

更新: 感谢@kriegaex对MCVE的支持,可以在this GitHub repository中找到它


共 (1) 个答案

  1. # 1 楼答案

    首先,从哪里获得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, unnamed module @4d518b32, loaded=false]
    [Byte Buddy] TRANSFORM org.apache.http.impl.client.CloseableHttpClient [jdk.internal.loader.ClassLoaders$AppClassLoader@2626b418, unnamed module @4d518b32, 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, loaded=false]
    Handling response: HTTP/1.1 200 OK [Date: Mon, 26 Oct 2020 05:21:25 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-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, Accept-Ranges: none, Vary: Accept-Encoding, Transfer-Encoding: chunked] org.apache.http.conn.BasicManagedEntity@5471388b
    Took 1274
    

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

    Implements the previously defined or matched method by the supplied implementation. A method interception is typically implemented in one of the following ways:

    1. If a method is declared by the instrumented type and the type builder creates a subclass or redefinition, any preexisting method is replaced by the given implementation. Any previously defined implementation is lost.
    2. If a method is declared by the instrumented type and the type builder creates a rebased version of the instrumented type, the original method is preserved within a private, synthetic method within the instrumented type. The original method therefore remains invokeable and is treated as the direct super method of the new method. When rebasing a type, it therefore becomes possible to invoke a non-virtual method's super method when a preexisting method body is replaced.
    3. If a virtual method is inherited from a super type, it is overridden. The overridden method is available for super method invocation.

    更新2:我的完整MCVE可以在this GitHub repository为大家提供方便