Migrate servlet instrumentation to byte buddy.
MODIFY
dd-java-agent/dd-java-agent.gradle
dd-java-agent/dd-java-agent.gradle
@@ -21,7 +21,8 @@ dependencies {
compile project(':dd-java-agent:tooling')
compile project(':dd-trace-annotations')
-
+ compile project(':dd-java-agent:integrations:servlet-2')
+ compile project(':dd-java-agent:integrations:servlet-3')
compile project(':dd-java-agent:integrations:spring-web')
compile deps.bytebuddy

DELETE
dd-java-agent/integrations/helpers/src/main/java/com/datadoghq/agent/integration/Servlet2Helper.java
-package com.datadoghq.agent.integration;
-
-import io.opentracing.ActiveSpan;
-import io.opentracing.NoopTracerFactory;
-import io.opentracing.SpanContext;
-import io.opentracing.Tracer;
-import io.opentracing.contrib.web.servlet.filter.HttpServletRequestExtractAdapter;
-import io.opentracing.contrib.web.servlet.filter.ServletFilterSpanDecorator;
-import io.opentracing.propagation.Format;
-import io.opentracing.tag.Tags;
-import java.util.Collections;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import lombok.extern.slf4j.Slf4j;
-import org.jboss.byteman.rule.Rule;
-
-/** Please be very careful not to introduce any Servlet 3 dependencies into this class. */
-@Slf4j
-public class Servlet2Helper extends OpenTracingHelper {
-
- /**
- * Used as a key of {@link HttpServletRequest#setAttribute(String, Object)} to inject server span
- * context
- */
- public static final String SERVER_SPAN_CONTEXT =
- Servlet2Helper.class.getName() + ".activeSpanContext";
-
- public static final String SERVLET_OPERATION_NAME = "servlet.request";
-
- protected final Tracer tracer;
-
- public Servlet2Helper(final Rule rule) {
- super(rule);
- Tracer tracerResolved;
- try {
- tracerResolved = getTracer();
- tracerResolved = tracerResolved == null ? NoopTracerFactory.create() : tracerResolved;
- } catch (final Exception e) {
- tracerResolved = NoopTracerFactory.create();
- log.warn("Failed to retrieve the tracer, using a NoopTracer instead: {}", e.getMessage());
- log.warn(e.getMessage(), e);
- }
- tracer = tracerResolved;
- }
-
- public void onRequest(final HttpServletRequest req, final HttpServletResponse resp) {
- if (req.getAttribute(SERVER_SPAN_CONTEXT) != null) {
- // Perhaps we're already tracing?
- return;
- }
-
- final SpanContext extractedContext =
- tracer.extract(Format.Builtin.HTTP_HEADERS, new HttpServletRequestExtractAdapter(req));
-
- final ActiveSpan span =
- tracer
- .buildSpan(SERVLET_OPERATION_NAME)
- .asChildOf(extractedContext)
- .withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_SERVER)
- .startActive();
-
- req.setAttribute(SERVER_SPAN_CONTEXT, span.context());
-
- ServletFilterSpanDecorator.STANDARD_TAGS.onRequest(req, span);
- }
-
- public void onError(
- final HttpServletRequest req, final HttpServletResponse resp, final Throwable ex) {
- if (req.getAttribute(SERVER_SPAN_CONTEXT) == null) {
- // Doesn't look like an active span was started at the beginning
- return;
- }
-
- final ActiveSpan span = tracer.activeSpan();
- if (span != null) {
- ServletFilterSpanDecorator.STANDARD_TAGS.onError(req, resp, ex, span);
- span.log(Collections.singletonMap("error.object", ex));
- span.deactivate();
- }
- }
-
- public void onResponse(final HttpServletRequest req, final HttpServletResponse resp) {
- if (req.getAttribute(SERVER_SPAN_CONTEXT) == null) {
- // Doesn't look like an active span was started at the beginning
- return;
- }
-
- final ActiveSpan span = tracer.activeSpan();
- if (span != null) {
- ServletFilterSpanDecorator.STANDARD_TAGS.onResponse(req, resp, span);
- span.deactivate();
- }
- }
-}

DELETE
dd-java-agent/integrations/helpers/src/main/java/com/datadoghq/agent/integration/Servlet3Helper.java
-package com.datadoghq.agent.integration;
-
-import io.opentracing.ActiveSpan;
-import io.opentracing.contrib.web.servlet.filter.ServletFilterSpanDecorator;
-import java.io.IOException;
-import java.util.Collections;
-import java.util.concurrent.atomic.AtomicBoolean;
-import javax.servlet.AsyncEvent;
-import javax.servlet.AsyncListener;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import lombok.extern.slf4j.Slf4j;
-import org.jboss.byteman.rule.Rule;
-
-@Slf4j
-public class Servlet3Helper extends Servlet2Helper {
-
- public Servlet3Helper(final Rule rule) {
- super(rule);
- }
-
- /**
- * The distinction with this method compared with Servlet2Helper.onResponse is the addition of the
- * async support.
- *
- * @param req
- * @param resp
- */
- @Override
- public void onResponse(final HttpServletRequest req, final HttpServletResponse resp) {
- if (req.getAttribute(SERVER_SPAN_CONTEXT) == null) {
- // Doesn't look like an active span was started at the beginning
- return;
- }
-
- final ActiveSpan span = tracer.activeSpan();
- if (span != null) {
- if (req.isAsyncStarted()) {
- addAsyncListeners(req, resp, span);
- } else {
- ServletFilterSpanDecorator.STANDARD_TAGS.onResponse(req, resp, span);
- }
- span.deactivate();
- }
- }
-
- private void addAsyncListeners(
- final HttpServletRequest req, final HttpServletResponse resp, final ActiveSpan span) {
-
- final ActiveSpan.Continuation cont = span.capture();
- final AtomicBoolean activated = new AtomicBoolean(false);
- // what if async is already finished? This would not be called
- req.getAsyncContext()
- .addListener(
- new AsyncListener() {
- @Override
- public void onComplete(final AsyncEvent event) throws IOException {
- if (activated.compareAndSet(false, true)) {
- try (ActiveSpan activeSpan = cont.activate()) {
- ServletFilterSpanDecorator.STANDARD_TAGS.onResponse(
- (HttpServletRequest) event.getSuppliedRequest(),
- (HttpServletResponse) event.getSuppliedResponse(),
- span);
- }
- }
- }
-
- @Override
- public void onTimeout(final AsyncEvent event) throws IOException {
- if (activated.compareAndSet(false, true)) {
- try (ActiveSpan activeSpan = cont.activate()) {
- ServletFilterSpanDecorator.STANDARD_TAGS.onTimeout(
- (HttpServletRequest) event.getSuppliedRequest(),
- (HttpServletResponse) event.getSuppliedResponse(),
- event.getAsyncContext().getTimeout(),
- span);
- }
- }
- }
-
- @Override
- public void onError(final AsyncEvent event) throws IOException {
- if (activated.compareAndSet(false, true)) {
- try (ActiveSpan activeSpan = cont.activate()) {
- ServletFilterSpanDecorator.STANDARD_TAGS.onError(
- (HttpServletRequest) event.getSuppliedRequest(),
- (HttpServletResponse) event.getSuppliedResponse(),
- event.getThrowable(),
- span);
- span.log(Collections.singletonMap("error.object", event.getThrowable()));
- }
- }
- }
-
- @Override
- public void onStartAsync(final AsyncEvent event) throws IOException {}
- });
- }
-}

MODIFY
dd-java-agent/integrations/servlet-2/servlet-2.gradle
dd-java-agent/integrations/servlet-2/servlet-2.gradle
@@ -9,3 +9,18 @@ versionScan {
"javax.servlet.FilterChain" : null,
]
}
+
+apply from: "${rootDir}/gradle/java.gradle"
+
+dependencies {
+ compileOnly group: 'javax.servlet', name: 'servlet-api', version: '2.3'
+
+ compile project(':dd-trace')
+ compile project(':dd-java-agent:tooling')
+
+ compile deps.bytebuddy
+ compile deps.opentracing
+
+ compile group: 'io.opentracing.contrib', name: 'opentracing-web-servlet-filter', version: '0.0.9'
+ compile group: 'com.google.auto.service', name: 'auto-service', version: '1.0-rc3'
+}

ADD
dd-java-agent/integrations/servlet-2/src/main/java/dd/inst/servlet2/HttpServlet2Instrumentation.java
+package dd.inst.servlet2;
+
+import static dd.trace.ClassLoaderHasClassMatcher.classLoaderHasClasses;
+import static net.bytebuddy.matcher.ElementMatchers.isProtected;
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static net.bytebuddy.matcher.ElementMatchers.not;
+import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
+
+import com.google.auto.service.AutoService;
+import dd.trace.Instrumenter;
+import io.opentracing.ActiveSpan;
+import io.opentracing.SpanContext;
+import io.opentracing.contrib.web.servlet.filter.HttpServletRequestExtractAdapter;
+import io.opentracing.contrib.web.servlet.filter.ServletFilterSpanDecorator;
+import io.opentracing.propagation.Format;
+import io.opentracing.tag.Tags;
+import io.opentracing.util.GlobalTracer;
+import java.util.Collections;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import net.bytebuddy.agent.builder.AgentBuilder;
+import net.bytebuddy.asm.Advice;
+
+@AutoService(Instrumenter.class)
+public final class HttpServlet2Instrumentation implements Instrumenter {
+ public static final String SERVLET_OPERATION_NAME = "servlet.request";
+
+ @Override
+ public AgentBuilder instrument(final AgentBuilder agentBuilder) {
+ return agentBuilder
+ .type(
+ named("javax.servlet.http.HttpServlet"),
+ not(classLoaderHasClasses("javax.servlet.AsyncEvent", "javax.servlet.AsyncListener"))
+ .and(
+ classLoaderHasClasses(
+ "javax.servlet.ServletContextEvent", "javax.servlet.FilterChain")))
+ .transform(
+ new AgentBuilder.Transformer.ForAdvice()
+ .advice(
+ named("service")
+ .and(takesArgument(0, named("javax.servlet.http.HttpServletRequest")))
+ .and(takesArgument(1, named("javax.servlet.http.HttpServletResponse")))
+ .and(isProtected()),
+ HttpServlet2Advice.class.getName()))
+ .asDecorator();
+ }
+
+ public static class HttpServlet2Advice {
+
+ @Advice.OnMethodEnter
+ public static ActiveSpan startSpan(@Advice.Argument(0) final HttpServletRequest req) {
+
+ final SpanContext extractedContext =
+ GlobalTracer.get()
+ .extract(Format.Builtin.HTTP_HEADERS, new HttpServletRequestExtractAdapter(req));
+
+ final ActiveSpan span =
+ GlobalTracer.get()
+ .buildSpan(SERVLET_OPERATION_NAME)
+ .asChildOf(extractedContext)
+ .withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_SERVER)
+ .startActive();
+
+ ServletFilterSpanDecorator.STANDARD_TAGS.onRequest(req, span);
+ return span;
+ }
+
+ @Advice.OnMethodExit(onThrowable = Throwable.class)
+ public static void stopSpan(
+ @Advice.Argument(0) final HttpServletRequest req,
+ @Advice.Argument(1) final HttpServletResponse resp,
+ @Advice.Enter final ActiveSpan span,
+ @Advice.Thrown final Throwable throwable) {
+
+ if (span != null) {
+ if (throwable != null) {
+ ServletFilterSpanDecorator.STANDARD_TAGS.onError(req, resp, throwable, span);
+ span.log(Collections.singletonMap("error.object", throwable));
+ } else {
+ ServletFilterSpanDecorator.STANDARD_TAGS.onResponse(req, resp, span);
+ }
+ span.deactivate();
+ }
+ }
+ }
+}

MODIFY
dd-java-agent/integrations/servlet-3/servlet-3.gradle
dd-java-agent/integrations/servlet-3/servlet-3.gradle
@@ -10,3 +10,18 @@ versionScan {
"javax.servlet.AsyncListener": null,
]
}
+
+apply from: "${rootDir}/gradle/java.gradle"
+
+dependencies {
+ compileOnly group: 'javax.servlet', name: 'javax.servlet-api', version: '3.0.1'
+
+ compile project(':dd-trace')
+ compile project(':dd-java-agent:tooling')
+
+ compile deps.bytebuddy
+ compile deps.opentracing
+
+ compile group: 'io.opentracing.contrib', name: 'opentracing-web-servlet-filter', version: '0.0.9'
+ compile group: 'com.google.auto.service', name: 'auto-service', version: '1.0-rc3'
+}

ADD
dd-java-agent/integrations/servlet-3/src/main/java/dd/inst/servlet3/HttpServlet3Instrumentation.java
+package dd.inst.servlet3;
+
+import static dd.trace.ClassLoaderHasClassMatcher.classLoaderHasClasses;
+import static net.bytebuddy.matcher.ElementMatchers.isProtected;
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
+
+import com.google.auto.service.AutoService;
+import dd.trace.Instrumenter;
+import io.opentracing.ActiveSpan;
+import io.opentracing.SpanContext;
+import io.opentracing.contrib.web.servlet.filter.HttpServletRequestExtractAdapter;
+import io.opentracing.contrib.web.servlet.filter.ServletFilterSpanDecorator;
+import io.opentracing.propagation.Format;
+import io.opentracing.tag.Tags;
+import io.opentracing.util.GlobalTracer;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.concurrent.atomic.AtomicBoolean;
+import javax.servlet.AsyncEvent;
+import javax.servlet.AsyncListener;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import net.bytebuddy.agent.builder.AgentBuilder;
+import net.bytebuddy.asm.Advice;
+
+@AutoService(Instrumenter.class)
+public final class HttpServlet3Instrumentation implements Instrumenter {
+ public static final String SERVLET_OPERATION_NAME = "servlet.request";
+
+ @Override
+ public AgentBuilder instrument(final AgentBuilder agentBuilder) {
+ return agentBuilder
+ .type(
+ named("javax.servlet.http.HttpServlet"),
+ classLoaderHasClasses("javax.servlet.AsyncEvent", "javax.servlet.AsyncListener"))
+ .transform(
+ new AgentBuilder.Transformer.ForAdvice()
+ .advice(
+ named("service")
+ .and(takesArgument(0, named("javax.servlet.http.HttpServletRequest")))
+ .and(takesArgument(1, named("javax.servlet.http.HttpServletResponse")))
+ .and(isProtected()),
+ HttpServlet3Advice.class.getName()))
+ .asDecorator();
+ }
+
+ public static class HttpServlet3Advice {
+
+ @Advice.OnMethodEnter
+ public static ActiveSpan startSpan(@Advice.Argument(0) final HttpServletRequest req) {
+
+ final SpanContext extractedContext =
+ GlobalTracer.get()
+ .extract(Format.Builtin.HTTP_HEADERS, new HttpServletRequestExtractAdapter(req));
+
+ final ActiveSpan span =
+ GlobalTracer.get()
+ .buildSpan(SERVLET_OPERATION_NAME)
+ .asChildOf(extractedContext)
+ .withTag(Tags.SPAN_KIND.getKey(), Tags.SPAN_KIND_SERVER)
+ .startActive();
+
+ ServletFilterSpanDecorator.STANDARD_TAGS.onRequest(req, span);
+ return span;
+ }
+
+ @Advice.OnMethodExit(onThrowable = Throwable.class)
+ public static void stopSpan(
+ @Advice.Argument(0) final HttpServletRequest req,
+ @Advice.Argument(1) final HttpServletResponse resp,
+ @Advice.Enter final ActiveSpan span,
+ @Advice.Thrown final Throwable throwable) {
+
+ if (span != null) {
+ if (throwable != null) {
+ ServletFilterSpanDecorator.STANDARD_TAGS.onError(req, resp, throwable, span);
+ span.log(Collections.singletonMap("error.object", throwable));
+ } else if (req.isAsyncStarted()) {
+ final ActiveSpan.Continuation cont = span.capture();
+ final AtomicBoolean activated = new AtomicBoolean(false);
+ // what if async is already finished? This would not be called
+ req.getAsyncContext().addListener(new TagSettingAsyncListener(activated, cont, span));
+ } else {
+ ServletFilterSpanDecorator.STANDARD_TAGS.onResponse(req, resp, span);
+ }
+ span.deactivate();
+ }
+ }
+
+ public static class TagSettingAsyncListener implements AsyncListener {
+ private final AtomicBoolean activated;
+ private final ActiveSpan.Continuation cont;
+ private final ActiveSpan span;
+
+ public TagSettingAsyncListener(
+ final AtomicBoolean activated,
+ final ActiveSpan.Continuation cont,
+ final ActiveSpan span) {
+ this.activated = activated;
+ this.cont = cont;
+ this.span = span;
+ }
+
+ @Override
+ public void onComplete(final AsyncEvent event) throws IOException {
+ if (activated.compareAndSet(false, true)) {
+ try (ActiveSpan activeSpan = cont.activate()) {
+ ServletFilterSpanDecorator.STANDARD_TAGS.onResponse(
+ (HttpServletRequest) event.getSuppliedRequest(),
+ (HttpServletResponse) event.getSuppliedResponse(),
+ span);
+ }
+ }
+ }
+
+ @Override
+ public void onTimeout(final AsyncEvent event) throws IOException {
+ if (activated.compareAndSet(false, true)) {
+ try (ActiveSpan activeSpan = cont.activate()) {
+ ServletFilterSpanDecorator.STANDARD_TAGS.onTimeout(
+ (HttpServletRequest) event.getSuppliedRequest(),
+ (HttpServletResponse) event.getSuppliedResponse(),
+ event.getAsyncContext().getTimeout(),
+ span);
+ }
+ }
+ }
+
+ @Override
+ public void onError(final AsyncEvent event) throws IOException {
+ if (event.getThrowable() != null && activated.compareAndSet(false, true)) {
+ try (ActiveSpan activeSpan = cont.activate()) {
+ ServletFilterSpanDecorator.STANDARD_TAGS.onError(
+ (HttpServletRequest) event.getSuppliedRequest(),
+ (HttpServletResponse) event.getSuppliedResponse(),
+ event.getThrowable(),
+ span);
+ span.log(Collections.singletonMap("error.object", event.getThrowable()));
+ }
+ }
+ }
+
+ @Override
+ public void onStartAsync(final AsyncEvent event) throws IOException {}
+ }
+ }
+}

MODIFY
dd-java-agent/integrations/spring-web/src/main/java/dd/inst/springweb/SpringWebInstrumentation.java
dd-java-agent/integrations/spring-web/src/main/java/dd/inst/springweb/SpringWebInstrumentation.java
package dd.inst.springweb;
+import static dd.trace.ClassLoaderHasClassWithFieldMatcher.classLoaderHasClassWithField;
import static net.bytebuddy.matcher.ElementMatchers.hasSuperType;
import static net.bytebuddy.matcher.ElementMatchers.isInterface;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
@@ -31,7 +32,10 @@ public final class SpringWebInstrumentation implements Instrumenter {
return agentBuilder
.type(
not(isInterface())
- .and(hasSuperType(named("org.springframework.web.servlet.HandlerAdapter"))))
+ .and(hasSuperType(named("org.springframework.web.servlet.HandlerAdapter"))),
+ classLoaderHasClassWithField(
+ "org.springframework.web.servlet.HandlerMapping",
+ "BEST_MATCHING_PATTERN_ATTRIBUTE"))
.transform(
new AgentBuilder.Transformer.ForAdvice()
.advice(

MODIFY
dd-java-agent/src/main/resources/initializer-rules.btm
dd-java-agent/src/main/resources/initializer-rules.btm
@@ -80,15 +80,3 @@ IF TRUE
DO
com.datadoghq.agent.InstrumentationRulesManager.registerClassLoad($0);
ENDRULE
-
-
-# Instrument Servlet
-# ===========================
-RULE HttpServlet-init
-CLASS ^javax.servlet.http.HttpServlet
-METHOD <init>
-AT EXIT
-IF TRUE
-DO
- com.datadoghq.agent.InstrumentationRulesManager.registerClassLoad($0);
-ENDRULE

MODIFY
dd-java-agent/src/main/resources/integration-rules.btm
dd-java-agent/src/main/resources/integration-rules.btm
@@ -106,73 +106,3 @@ IF TRUE
DO
patch($0)
ENDRULE
-
-# Instrument Servlet 2
-# ===========================
-RULE HttpServlet-2.service-entry
-CLASS ^javax.servlet.http.HttpServlet
-METHOD service(HttpServletRequest, HttpServletResponse)
-HELPER com.datadoghq.agent.integration.Servlet2Helper
-COMPILE
-AT ENTRY
-IF TRUE
-DO
- onRequest($1, $2)
-ENDRULE
-
-RULE HttpServlet-2.service-exit
-CLASS ^javax.servlet.http.HttpServlet
-METHOD service(HttpServletRequest, HttpServletResponse)
-HELPER com.datadoghq.agent.integration.Servlet2Helper
-COMPILE
-AT EXIT
-IF TRUE
-DO
- onResponse($1, $2)
-ENDRULE
-
-RULE HttpServlet-2.service-error
-CLASS ^javax.servlet.http.HttpServlet
-METHOD service(HttpServletRequest, HttpServletResponse)
-HELPER com.datadoghq.agent.integration.Servlet2Helper
-COMPILE
-AT EXCEPTION EXIT
-IF TRUE
-DO
- onError($1, $2, $^)
-ENDRULE
-
-# Instrument Servlet 3
-# ===========================
-RULE HttpServlet-3.service-entry
-CLASS ^javax.servlet.http.HttpServlet
-METHOD service(HttpServletRequest, HttpServletResponse)
-HELPER com.datadoghq.agent.integration.Servlet3Helper
-COMPILE
-AT ENTRY
-IF TRUE
-DO
- onRequest($1, $2)
-ENDRULE
-
-RULE HttpServlet-3.service-exit
-CLASS ^javax.servlet.http.HttpServlet
-METHOD service(HttpServletRequest, HttpServletResponse)
-HELPER com.datadoghq.agent.integration.Servlet3Helper
-COMPILE
-AT EXIT
-IF TRUE
-DO
- onResponse($1, $2)
-ENDRULE
-
-RULE HttpServlet-3.service-error
-CLASS ^javax.servlet.http.HttpServlet
-METHOD service(HttpServletRequest, HttpServletResponse)
-HELPER com.datadoghq.agent.integration.Servlet3Helper
-COMPILE
-AT EXCEPTION EXIT
-IF TRUE
-DO
- onError($1, $2, $^)
-ENDRULE

ADD
dd-java-agent/tooling/src/main/java/dd/trace/ClassLoaderHasClassMatcher.java
+package dd.trace;
+
+import net.bytebuddy.matcher.ElementMatcher;
+
+public class ClassLoaderHasClassMatcher extends ElementMatcher.Junction.AbstractBase<ClassLoader> {
+
+ private final String[] names;
+
+ private ClassLoaderHasClassMatcher(final String... names) {
+ this.names = names;
+ }
+
+ public static ElementMatcher.Junction.AbstractBase<ClassLoader> classLoaderHasClasses(
+ final String... names) {
+ return new ClassLoaderHasClassMatcher(names);
+ }
+
+ @Override
+ public boolean matches(final ClassLoader target) {
+ try {
+ if (target != null) {
+ for (final String name : names) {
+ Class.forName(name, false, target);
+ }
+ return true;
+ }
+ return false;
+ } catch (final ClassNotFoundException e) {
+ return false;
+ }
+ }
+}

ADD
dd-java-agent/tooling/src/main/java/dd/trace/ClassLoaderHasClassWithFieldMatcher.java
+package dd.trace;
+
+import net.bytebuddy.matcher.ElementMatcher;
+
+public class ClassLoaderHasClassWithFieldMatcher
+ extends ElementMatcher.Junction.AbstractBase<ClassLoader> {
+
+ private final String className;
+ private final String fieldName;
+
+ private ClassLoaderHasClassWithFieldMatcher(final String className, final String fieldName) {
+ this.className = className;
+ this.fieldName = fieldName;
+ }
+
+ public static AbstractBase<ClassLoader> classLoaderHasClassWithField(
+ final String className, final String fieldName) {
+ return new ClassLoaderHasClassWithFieldMatcher(className, fieldName);
+ }
+
+ @Override
+ public boolean matches(final ClassLoader target) {
+ try {
+ if (target != null) {
+ final Class<?> aClass = Class.forName(className, false, target);
+ aClass.getDeclaredField(fieldName);
+ return true;
+ }
+ return false;
+ } catch (final ClassNotFoundException e) {
+ return false;
+ } catch (final NoSuchFieldException e) {
+ return false;
+ }
+ }
+}

Migrate servlet instrumentation to byte buddy.