2015-12-30 106 views
2

我试图用Bytebuddy编写一个javaagent来拦截apache httpclient请求,并且我想使用此代理程序用于spring启动应用程序。当我从Idea开始测试Spring引导应用程序时,代理工作正常(直接运行main方法)。但是,当我将应用程序打包到弹簧引导超级罐中并使用java -javaagent:myagent.jar -jar myapplication.jar, 运行它时,它会引发以下异常。无法使用javaagent为spring启动超级jar应用程序的Apache httpclient

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.<init>(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.<init>(HttpUtil.java:118) 
    at com.yjf.common.net.HttpUtil.<init>(HttpUtil.java:81) 
    at com.yjf.common.net.HttpUtil.<clinit>(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<CloseableHttpResponse> 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引导uber jar引导应用程序的方式引起的。 Spring引导提供了一个名为LaunchedURLClassLoader的专用类加载器,用于从uber jar加载与应用程序相关的类,而javaagent jar由默认的系统类加载器(如果我的理解正确的话)加载。所以apache httpclient lib(包含在uber jar中)对于系统类加载器是不可见的。

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

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

其他信息来源可能会有所帮助:
弹簧引导版本1.3.1.RELEASE
字节哥们0.7.7,打包成使用代理Maven的组装插件的JAR-与依赖性descriptorRef
阿帕奇的httpclient 4.3.2

回答

1

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

  1. 使用Spring Boot的专用ClassLoader到deligate到卸载类型:

    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 罐子到Spring Boot的尤伯杯罐子相关intecepted类使得委托类可以引用类。

此问题在Byte Buddy's issue tracker中有更详细的讨论。