无法使用javaagent为apach httpclient设置spring boot uber jar application

时间:2015-12-30 05:20:04

标签: java spring-boot javaagents uberjar byte-buddy

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

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 boot uber jar引导应用程序引起的。 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 个答案:

答案 0 :(得分:1)

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

  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 中,以便委托者类可以引用与所接受的类相关的类。

  3. Byte Buddy's issue tracker中详细讨论了这个问题。