简体   繁体   中英

byte-buddy throws java.lang.ClassNotFoundException: javax.servlet.http.HttpServlet

I'm trying to instrument calls to the service() method of javax.servlet.http.HttpServlet using a Java agent based on Byte Buddy. The premain function in my code is called properly but instrumentation fails with the stack trace:

java.lang.reflect.InvocationTargetException
    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:498)
      at     sun.instrument.InstrumentationImpl.loadClassAndStartAgent(InstrumentationImpl.java:386)
  at sun.instrument.InstrumentationImpl.loadClassAndCallPremain(InstrumentationImpl.java:401)
Caused by: java.lang.NoClassDefFoundError: javax/servlet/http/HttpServlet
    at ub.jagent.ServletInstrumentation.instrument(ServletInstrumentation.java:24)
    at ub.jagent.StackTracerAgent.premain(StackTracerAgent.java:75)
    ... 6 more
Caused by: java.lang.ClassNotFoundException: javax.servlet.http.HttpServlet
    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)
    ... 8 more
FATAL ERROR in native method: processing of -javaagent failed

I use the agent builder as follows:

AgentBuilder b = new AgentBuilder.Default().ignore(ElementMatchers.none())
        .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
        .with(AgentBuilder.InitializationStrategy.NoOp.INSTANCE)
        .with(AgentBuilder.TypeStrategy.Default.REDEFINE)   
        .type(ElementMatchers.isSubTypeOf(HttpServlet.class))
            .transform((builder, type, classLoader) -> 
                builder.visit(Advice.to(ServletAdvice.class) 
                .on(ElementMatchers.named("service")
                .and(ElementMatchers.takesArgument(0, HttpServletRequest.class))
                .and(ElementMatchers.takesArgument(1, HttpServletResponse.class)))).installOn(inst);

And my Advice implementation looks like:

public class ServletAdvice {

    @Advice.OnMethodEnter
    public static void before(@Advice.This Object obj,
                               @Advice.Argument(value = 0) HttpServletRequest a1,
                               @Advice.Argument(value = 1) HttpServletResponse a2,
                               @Advice.Origin Method method) 
        {
            System.out.println("\nRunning pre-method logic for " + obj.getClass().getCanonicalName() + ":" + method.getName() + "()");
        <...etc...>
        }
    }
}

Oddly, when I match on the specific Tomcat implementation class:

.type(ElementMatchers.named("org.glassfish.jersey.servlet.ServletContainer"))

...then agent setup works (although the references to HttpServletRequest and HttpServletResponse in the Advice method trigger ClassNotFoundException during instrumentation itself).

Does this mean I have to include all the agent dependencies (including the Java Servlet API) in the agent .jar file itself?

I'm using ByteBuddy 1.6.2, Apache Tomcat v8.0.

EDIT Revised and working code (with Rafael's help) looks like:

         .type(ElementMatchers.isSubTypeOf(new TypeDescription.Latent("javax.servlet.GenericServlet", Modifier.PUBLIC | Modifier.ABSTRACT, TypeDescription.Generic.OBJECT, null)))
         .transform(
                 new AgentBuilder.Transformer.ForAdvice()
                    .include(getClass().getClassLoader())
                    .advice(
                        ElementMatchers.named("service")
                            .and(ElementMatchers.takesArgument(0, ElementMatchers.named("javax.servlet.http.HttpServletRequest"))
                            .and(ElementMatchers.takesArgument(1, ElementMatchers.named( "javax.servlet.http.HttpServletResponse")),
                        ServletAdvice.class.getName())
         );

I assume that the servlet-family of types is not available to the class loader that Byte Buddy is using for resolving the advice. To overcome this, Byte Buddy offers a specific transformer that resolves the advice class for both the user's and the agent's class loader:

new AgentBuilder.Transformation.ForAdvice()
  .include(getClass().getClassLoader())
  .advice("your.pkg.ServletAdvice")

As for the element matchers, rather match the arguments' names then using the type constants.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM