无法使用javaagent为apach httpclient设置spring boot uber jar应用程序

我正在尝试用Bytebuddy编写一个javaagent来拦截apache httpclient请求,我想将这个代理用于spring boot应用程序。 当我从Idea(直接运行main方法)启动测试Spring启动应用程序时,代理工作正常。 但是,当我将应用程序打包到spring boot uber jar并使用java -javaagent:myagent.jar -jar myapplication.jar运行它时,它会抛出以下exception。

 onError:org.apache.http.impl.client.AbstractHttpClient java.lang.NoClassDefFoundError: org/apache/http/HttpHost at java.lang.Class.getDeclaredMethods0(Native Method) at java.lang.Class.privateGetDeclaredMethods(Class.java:2701) at java.lang.Class.getDeclaredMethods(Class.java:1975) at net.bytebuddy.description.method.MethodList$ForLoadedType.(MethodList.java:106) at net.bytebuddy.description.type.TypeDescription$ForLoadedType.getDeclaredMethods(TypeDescription.java:985) at net.bytebuddy.implementation.MethodDelegation$MethodContainer$ForExplicitMethods.ofStatic(MethodDelegation.java:1037) at net.bytebuddy.implementation.MethodDelegation.to(MethodDelegation.java:247) at net.bytebuddy.implementation.MethodDelegation.to(MethodDelegation.java:226) at com.yiji.dtrace.agent.httpclient4.interceptor.HttpClient4Interceptors$1.transform(HttpClient4Interceptors.java:48) at net.bytebuddy.agent.builder.AgentBuilder$Transformer$Compound.transform(AgentBuilder.java:457) at net.bytebuddy.agent.builder.AgentBuilder$Default$Transformation$Simple$Resolution.apply(AgentBuilder.java:2791) at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.transform(AgentBuilder.java:3081) at sun.instrument.TransformerManager.transform(TransformerManager.java:188) at sun.instrument.InstrumentationImpl.transform(InstrumentationImpl.java:428) at java.lang.ClassLoader.defineClass1(Native Method) at java.lang.ClassLoader.defineClass(ClassLoader.java:760) at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142) at java.net.URLClassLoader.defineClass(URLClassLoader.java:467) at java.net.URLClassLoader.access$100(URLClassLoader.java:73) at java.net.URLClassLoader$1.run(URLClassLoader.java:368) at java.net.URLClassLoader$1.run(URLClassLoader.java:362) at java.security.AccessController.doPrivileged(Native Method) at java.net.URLClassLoader.findClass(URLClassLoader.java:361) at org.springframework.boot.loader.LaunchedURLClassLoader.doLoadClass(LaunchedURLClassLoader.java:170) at org.springframework.boot.loader.LaunchedURLClassLoader.loadClass(LaunchedURLClassLoader.java:142) at java.lang.ClassLoader.loadClass(ClassLoader.java:357) at java.lang.ClassLoader.defineClass1(Native Method) at java.lang.ClassLoader.defineClass(ClassLoader.java:760) at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142) at java.net.URLClassLoader.defineClass(URLClassLoader.java:467) at java.net.URLClassLoader.access$100(URLClassLoader.java:73) at java.net.URLClassLoader$1.run(URLClassLoader.java:368) at java.net.URLClassLoader$1.run(URLClassLoader.java:362) at java.security.AccessController.doPrivileged(Native Method) at java.net.URLClassLoader.findClass(URLClassLoader.java:361) at org.springframework.boot.loader.LaunchedURLClassLoader.doLoadClass(LaunchedURLClassLoader.java:170) at org.springframework.boot.loader.LaunchedURLClassLoader.loadClass(LaunchedURLClassLoader.java:142) at java.lang.ClassLoader.loadClass(ClassLoader.java:357) at java.lang.ClassLoader.defineClass1(Native Method) at java.lang.ClassLoader.defineClass(ClassLoader.java:760) at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142) at java.net.URLClassLoader.defineClass(URLClassLoader.java:467) at java.net.URLClassLoader.access$100(URLClassLoader.java:73) at java.net.URLClassLoader$1.run(URLClassLoader.java:368) at java.net.URLClassLoader$1.run(URLClassLoader.java:362) at java.security.AccessController.doPrivileged(Native Method) at java.net.URLClassLoader.findClass(URLClassLoader.java:361) at org.springframework.boot.loader.LaunchedURLClassLoader.doLoadClass(LaunchedURLClassLoader.java:170) at org.springframework.boot.loader.LaunchedURLClassLoader.loadClass(LaunchedURLClassLoader.java:142) at java.lang.ClassLoader.loadClass(ClassLoader.java:357) at com.yjf.common.net.HttpUtil.(HttpUtil.java:118) at com.yjf.common.net.HttpUtil.(HttpUtil.java:81) at com.yjf.common.net.HttpUtil.(HttpUtil.java:78) at com.daidai.dtrace.agent.test.Main.main(Main.java:35) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:497) at org.springframework.boot.loader.MainMethodRunner.run(MainMethodRunner.java:53) at java.lang.Thread.run(Thread.java:745) Caused by: java.lang.ClassNotFoundException: org.apache.http.HttpHost at java.net.URLClassLoader.findClass(URLClassLoader.java:381) at java.lang.ClassLoader.loadClass(ClassLoader.java:424) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331) at java.lang.ClassLoader.loadClass(ClassLoader.java:357) ... 60 more 

这是我的代理相关代码。

 public class DTraceAgent { public static TypeDescription abstractHttpClientDescription() { return new TypeDescription.Latent("org.apache.http.impl.client.AbstractHttpClient", Modifier.PUBLIC|Modifier.ABSTRACT, TypeDescription.OBJECT, Arrays.asList(httpClientDescription())); } public static TypeDescription httpHostDescription() { return new TypeDescription.Latent("org.apache.http.HttpHost", Modifier.PUBLIC|Modifier.FINAL, TypeDescription.OBJECT, Arrays.asList(new TypeDescription.ForLoadedType(Cloneable.class), new TypeDescription.ForLoadedType(Serializable.class))); } public static TypeDescription httpContextDescription() { return new TypeDescription.Latent("org.apache.http.protocol.HttpContext", getInterfaceModifiers(), TypeDescription.OBJECT, null); } public static TypeDescription httpRequestDescription() { return new TypeDescription.Latent("org.apache.http.HttpRequest", getInterfaceModifiers(), httpMessageDescription(), null); } public static void premain(String arguments, Instrumentation instrumentation) { new AgentBuilder.Default() //.withBinaryLocator(binaryLocatorFor(instrumentation)) .withListener(DebugListener.getListener()) .type(is(abstractHttpClientDescription())) .transform(new AgentBuilder.Transformer() { public DynamicType.Builder transform(DynamicType.Builder builder, TypeDescription typeDescription) { return builder.method(named("execute") .and(takesArguments(httpHostDescription(), httpRequestDescription(), httpContextDescription())) .and(returns(named("org.apache.http.HttpResponse")))) .intercept(MethodDelegation.to(HttpClientInterceptor4dot3Plus.class)); } }).installOn(instrumentation); } } public class HttpClientInterceptor4dot3Plus { public static CloseableHttpResponse doExecute( @SuperCall Callable client, @Argument(1)HttpRequest request ) throws Exception { StringBuilder builder = new StringBuilder(1024); if (request != null && request.getRequestLine() != null) { RequestLine requestLine = request.getRequestLine(); builder.append(requestLine.getMethod()).append(" ").append(requestLine.getUri()); } try (TraceScope scope = Trace.startSpanForEntry(builder.toString())) { Trace.spanType(Span.SPAN_TYPE_HTTP); try { return client.call(); } catch (Exception e) { Trace.exception(e); throw e; } } } } public class DebugListener { public static AgentBuilder.Listener getListener() { return new AgentBuilder.Listener() { @Override public void onTransformation(TypeDescription typeDescription, DynamicType dynamicType) { System.err.println("onTransformation:" + typeDescription.getCanonicalName()); try { dynamicType.saveIn(new File("generated_classes")); } catch (IOException e) { e.printStackTrace(); } } @Override public void onIgnored(TypeDescription typeDescription) { //System.err.println("onIgored:" + typeDescription.getCanonicalName()); } @Override public void onError(String typeName, Throwable throwable) { System.err.println("onError:" + typeName); throwable.printStackTrace(); } @Override public void onComplete(String typeName) { //System.err.println("onComplete:" + typeName); } }; } } 

我认为这个问题是由spring boot uber jar bootstraps应用程序引起的。 Spring引导提供了一个名为LaunchedURLClassLoader的专用类加载器,用于从超级jar加载与应用程序相关的类,而javaagent jar是由默认的系统类加载器加载的(如果我的理解是正确的)。 因此系统类加载器看不到apache httpclient lib(包含在uber jar中)。

我试图为AgentBuilder提供一个BinaryLocator,但它没有用。 也许BinaryLocator没有正确构造。 无论如何,一个合适的BinaryLocator可能是一个可能的解决方案。

非常感谢任何解决方案或建议。

其他信息可能会有所帮助:
spring-boot version 1.3.1.RELEASE
byte-buddy 0.7.7,使用maven-assembly-plugin的jar-with-dependencies descriptorRef打包到代理中
apache httpclient 4.3.2

我通过两个步骤解决了这个问题:

  1. 使用Spring Boot的专用ClassLoader来转移到卸载类型:

     public static void premain(String arguments, Instrumentation instrumentation) { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); ClassFileLocator.Compound compound = new ClassFileLocator.Compound(ClassFileLocator.ForClassLoader.of(classLoader), ClassFileLocator.ForClassLoader.ofClassPath()); TypeDescription delegator = TypePool.Default.of(compound).describe(delegatorClass).resolve(); new AgentBuilder.Default() //.withBinaryLocator(binaryLocatorFor(instrumentation)) .withListener(DebugListener.getListener()) .type(is(abstractHttpClientDescription())) .transform(new AgentBuilder.Transformer() { @Override public DynamicType.Builder transform(DynamicType.Builder builder, TypeDescription typeDescription) { return builder.method(named("execute") .and(takesArguments(httpHostDescription(), httpRequestDescription(), httpContextDescription())) .and(returns(named("org.apache.http.HttpResponse")))) .intercept(MethodDelegation.to(delegator)); } }).installOn(instrumentation); } 
  2. 将javaagent jar打包到Spring Boot的uber-jar中,这样委托者类就可以引用与被截取的类相关的类。

在Byte Buddy的问题跟踪器中更详细地讨论了这个问题。