字节预算转换一次执行多次 [英] Byte-buddy transform running multiple times for a single execution

查看:68
本文介绍了字节预算转换一次执行多次的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我写了如下的 javaagent 来捕获apache org.apache.http.client.HttpClient execute 方法的执行时间.>.它正在捕获时间,但它运行了三遍.

  import java.lang.instrument.Instrumentation;导入net.bytebuddy.agent.builder.AgentBuilder;导入net.bytebuddy.implementation.MethodDelegation;导入静态net.bytebuddy.matcher.ElementMatchers.isMethod;导入静态net.bytebuddy.matcher.ElementMatchers.isAbstract;导入静态net.bytebuddy.matcher.ElementMatchers.takesArguments;导入静态net.bytebuddy.matcher.ElementMatchers.takesArgument;导入静态net.bytebuddy.matcher.ElementMatchers.named;导入静态net.bytebuddy.matcher.ElementMatchers.not;公共类TimerAgent {public static void premain(字符串参数,仪器仪表){新的AgentBuilder.Default().类型(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,命名为("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);}}导入java.lang.reflect.Method;导入java.util.concurrent.Callable;导入net.bytebuddy.implementation.bind.annotation.Origin;导入net.bytebuddy.implementation.bind.annotation.RuntimeType;导入net.bytebuddy.implementation.bind.annotation.SuperCall;公共类TimingInterceptor {@RuntimeType()公共静态对象拦截器(@Origin Method方法,@SuperCall Callable<?>可召回的){长期开始= System.currentTimeMillis();尝试 {尝试 {返回callable.call();} catch(Exception e){e.printStackTrace();}} 最后 {System.out.println("接听"+(System.currentTimeMillis()-开始));}返回0;}} 

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

  import java.io.IOException;导入org.apache.http.HttpEntity;导入org.apache.http.HttpResponse;导入org.apache.http.client.ClientProtocolException;导入org.apache.http.client.HttpClient;导入org.apache.http.client.ResponseHandler;导入org.apache.http.client.methods.HttpGet;导入org.apache.http.client.methods.HttpUriRequest;导入org.apache.http.impl.client.DefaultHttpClient;公共班级主要{公共静态void main(String [] args)引发IOException {HttpClient客户端=新的DefaultHttpClient();HttpUriRequest httpUriRequest =新的HttpGet("http://www.google.com");HttpResponse响应=客户端.execute(httpUriRequest,new ResponseHandler< HttpResponse>(){公共HttpResponse handleResponse(最终HttpResponse响应)引发ClientProtocolException,IOException {返回响应;}});}} 

控制台输出:

 读取512拿走了512拿走了512 

Maven依赖项:

 < dependency>< groupId> net.bytebuddy</groupId>< artifactId>字节预算</artifactId>< version> 1.10.10</version></dependency><依赖关系>< groupId> org.apache.httpcomponents</groupId>< artifactId> httpasyncclient</artifactId>< version> 4.1.4</version>< scope>提供的</scope></dependency> 

因此,您要么需要限制元素匹配器,要么必须忍受多个日志行.

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

似乎拦截器匹配所有类,即使从技术上讲该方法仅在 CloseableHttpClient 中定义,而在 AbstractHttpClient DefaultHttpClient .限制类型匹配的一种方法是检查目标类是否确实包含您要检测的方法:

  ElementMatcher.Junction< MethodDescription>executeMethodDecription = isMethod().and(named("execute"))).and(not(isAbstract())).and(takesArguments(3)).and(takesArgument(0,命名为("org.apache.http.client.methods.HttpUriRequest")))).and(takesArgument(1,named("org.apache.http.client.ResponseHandler")))).and(takesArgument(2,named("org.apache.http.protocol.HttpContext"))));新的AgentBuilder.Default().with(AgentBuilder.Listener.StreamWriting.toSystemError().withTransformationsOnly()).with(AgentBuilder.InstallationListener.StreamWriting.toSystemError()).类型(isSubTypeOf(HttpClient.class).and(declaresMethod(executeMethodDecription))).transform((builder,type,classLoader,module)->生成器.method(executeMethodDecription).intercept(MethodDelegation.to(TimingInterceptor.class))).installOn(instrumentation); 

现在控制台日志应变为:

 <代码> [字节好友] BEFORE_INSTALL net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer$ByteBuddy$ModuleSupport@67080771 on sun.instrument.InstrumentationImpl@72cde7cc[Byte Buddy]在sun.instrument.InstrumentationImpl@72cde7cc上安装net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer$ByteBuddy$ModuleSupport@67080771[字节好友] TRANSFORM org.apache.http.impl.client.CloseableHttpClient [jdk.internal.loader.ClassLoaders$AppClassLoader@2626b418,未命名的模块@ 6ea2bc93,loaded = false]处理响应:HTTP/1.1 200 OK [日期:2020年10月26日星期一,格林尼治标准时间,过期:-1,缓存控制:私有,max-age = 0,内容类型:text/html;charset = ISO-8859-1,P3P:CP =这不是P3P策略!有关更多信息,请参见g.co/p3phelp.",服务器:gws,X-XSS-Protection:0,X-Frame-Options:SAMEORIGIN,Set-Cookie:1P_JAR = 2020-10-26-05;expires =星期三,2020年11月25日05:21:25 GMT;路径=/;domain = .google.com;安全,的Set-Cookie:NID = 204 = N7U6SBBW5jjM1hynbowChQ2TyMSbiLHHwioKYusPVHzJPkiRaSvpmeIlHipo34BAq5QqlJnD7GDD1iv6GhIZlEEl7k3MclOxNY9WGn9c6elHikj6MPUhXsAapYz9pOVFl_DjAInWv5pI00FfUZ6i5mK14kq3JIXu-AV84WKDxdc;expires =星期二,2021年4月27日05:21:25 GMT;路径=/;domain = .google.com;HttpOnly,接受范围:无,变化:接受编码,传输编码:分块] org.apache.http.conn.BasicManagedEntity@5471388b拿了1274 


更新:实际上, intercept()的行为在其 MCVE 可在This is the implementation for implementsInterface

This is how I am executing the application:

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

I am not sure what is causing it to print 3 times.

Update: Thanks to @kriegaex for the MCVE, it can be found in this GitHub repository.

解决方案

First of all, where do you get the implementsInterface element matcher from? It is not part of ByteBuddy, at least not in the current version. I replaced it by isSubTypeOf in order to make the code compile. Anyway, if you activate logging like this

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

you will see something like this on the console:

[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

See? You have installed your interceptor in the whole class hierarchy, three classes all in all:

Hence, you either need to limit your element matcher or have to live with multiple log lines.

Disclaimer: I am not a ByteBuddy expert and maybe Rafael Winterhalter will write a much better answer later, maybe my solution is not canonical.

It looks as if the interceptor matches all classes, even though technically the method is only defined in CloseableHttpClient and not overridden in AbstractHttpClient or DefaultHttpClient. One way to limit type matching is to check if the target class actually contains the method you wish to instrument:

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

Now the console log should turn into:

[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


Update: Actually the behaviour of intercept() is described in its 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.


Update 2: My full MCVE is available for everyone's convenience in this GitHub repository.

这篇关于字节预算转换一次执行多次的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆