单次执行多次字节伙伴转换 运行
Byte-buddy transform running multiple times for a single execution
我如下写了一个javaagent
来捕获apacheorg.apache.http.client.HttpClient
的execute
方法的执行时间。它正在捕捉时间,但它是 运行 三倍。
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>
This 是 implementsInterface
的实现
这就是我执行应用程序的方式:
java -javaagent:"javaagent.jar" -jar application.jar
我不确定是什么导致它打印 3 次。
更新:
感谢@kriegaex MCVE, it can be found in 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, 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
看到了吗?您已经在整个 class 层次结构中安装了拦截器,总共三个 class:
因此,您要么需要限制元素匹配器,要么必须忍受多行日志。
免责声明:我不是 ByteBuddy 专家,也许 Rafael Winterhalter 稍后会写出更好的答案,也许我的解决方案不规范。
看起来好像拦截器匹配所有 classes,即使从技术上讲该方法仅在 CloseableHttpClient
中定义并且未在 AbstractHttpClient
或 DefaultHttpClient
中覆盖。限制类型匹配的一种方法是检查目标 class 是否实际包含您希望检测的方法:
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:
- 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.
- 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.
- If a virtual method is inherited from a super type, it is overridden. The overridden method is available for super method invocation.
更新 2: 我的完整 MCVE is available for everyone's convenience in this GitHub repository.
我如下写了一个javaagent
来捕获apacheorg.apache.http.client.HttpClient
的execute
方法的执行时间。它正在捕捉时间,但它是 运行 三倍。
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>
This 是 implementsInterface
这就是我执行应用程序的方式:
java -javaagent:"javaagent.jar" -jar application.jar
我不确定是什么导致它打印 3 次。
更新: 感谢@kriegaex MCVE, it can be found in 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, 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
看到了吗?您已经在整个 class 层次结构中安装了拦截器,总共三个 class:
因此,您要么需要限制元素匹配器,要么必须忍受多行日志。
免责声明:我不是 ByteBuddy 专家,也许 Rafael Winterhalter 稍后会写出更好的答案,也许我的解决方案不规范。
看起来好像拦截器匹配所有 classes,即使从技术上讲该方法仅在 CloseableHttpClient
中定义并且未在 AbstractHttpClient
或 DefaultHttpClient
中覆盖。限制类型匹配的一种方法是检查目标 class 是否实际包含您希望检测的方法:
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:
- 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.
- 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.
- If a virtual method is inherited from a super type, it is overridden. The overridden method is available for super method invocation.
更新 2: 我的完整 MCVE is available for everyone's convenience in this GitHub repository.