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())
);
erausreto the argument matchers but this is fixed in 1.6.7. - Rafael Winterhalter