From 72064d68498dc0573ed2ced53318541a2e5c97a6 Mon Sep 17 00:00:00 2001 From: Douglas Q Hawkins Date: Thu, 16 Apr 2026 17:18:18 -0400 Subject: [PATCH] Cache handler span keys in spring-webmvc to avoid per-request string concatenation The handler span key (used as a request attribute name) was computed via string concatenation on every request entry and exit. Replace with a ClassValue-based cache in SpringWebHttpServerDecorator so each handler class computes its key only once. Uses GenericClassValue.of() following the established codebase pattern to avoid anonymous inner class issues with ByteBuddy helper class injection. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../HandlerAdapterInstrumentation.java | 20 +++++++++---------- ...InvocableHandlerMethodInstrumentation.java | 12 +++++------ .../SpringWebHttpServerDecorator.java | 16 +++++++++++++++ .../springweb6/ControllerAdvice.java | 20 +++++++++---------- .../SpringWebHttpServerDecorator.java | 16 +++++++++++++++ .../WrapContinuableResultAdvice.java | 11 +++++----- 6 files changed, 62 insertions(+), 33 deletions(-) diff --git a/dd-java-agent/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/src/main/java/datadog/trace/instrumentation/springweb/HandlerAdapterInstrumentation.java b/dd-java-agent/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/src/main/java/datadog/trace/instrumentation/springweb/HandlerAdapterInstrumentation.java index 6558878f8ad..70b1f46048b 100644 --- a/dd-java-agent/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/src/main/java/datadog/trace/instrumentation/springweb/HandlerAdapterInstrumentation.java +++ b/dd-java-agent/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/src/main/java/datadog/trace/instrumentation/springweb/HandlerAdapterInstrumentation.java @@ -9,9 +9,9 @@ import static datadog.trace.bootstrap.instrumentation.api.Java8BytecodeBridge.getRootContext; import static datadog.trace.bootstrap.instrumentation.api.Java8BytecodeBridge.spanFromContext; import static datadog.trace.bootstrap.instrumentation.decorator.HttpServerDecorator.DD_CONTEXT_ATTRIBUTE; -import static datadog.trace.instrumentation.springweb.SpringWebHttpServerDecorator.DD_HANDLER_SPAN_CONTINUE_SUFFIX; -import static datadog.trace.instrumentation.springweb.SpringWebHttpServerDecorator.DD_HANDLER_SPAN_PREFIX_KEY; import static datadog.trace.instrumentation.springweb.SpringWebHttpServerDecorator.DECORATE; +import static datadog.trace.instrumentation.springweb.SpringWebHttpServerDecorator.handlerSpanContinueKey; +import static datadog.trace.instrumentation.springweb.SpringWebHttpServerDecorator.handlerSpanKey; import static net.bytebuddy.matcher.ElementMatchers.isMethod; import static net.bytebuddy.matcher.ElementMatchers.isPublic; import static net.bytebuddy.matcher.ElementMatchers.takesArgument; @@ -71,8 +71,8 @@ public static class ControllerAdvice { public static ContextScope nameResourceAndStartSpan( @Advice.Argument(0) final HttpServletRequest request, @Advice.Argument(2) final Object handler, - @Advice.Local("handlerSpanKey") String handlerSpanKey) { - handlerSpanKey = ""; + @Advice.Local("handlerClass") Class handlerClass) { + handlerClass = null; // Name the parent span based on the matching pattern Object contextObj = request.getAttribute(DD_CONTEXT_ATTRIBUTE); @@ -90,13 +90,12 @@ public static ContextScope nameResourceAndStartSpan( // Now create a span for handler/controller execution. - final String handlerKey; if (handler instanceof HandlerMethod) { - handlerKey = ((HandlerMethod) handler).getBean().getClass().getName(); + handlerClass = ((HandlerMethod) handler).getBean().getClass(); } else { - handlerKey = handler.getClass().getName(); + handlerClass = handler.getClass(); } - handlerSpanKey = DD_HANDLER_SPAN_PREFIX_KEY + handlerKey; + final String handlerSpanKey = handlerSpanKey(handlerClass); // If the context already exists, return it final Object existingContext = request.getAttribute(handlerSpanKey); @@ -117,13 +116,12 @@ public static void stopSpan( @Advice.Argument(0) final HttpServletRequest request, @Advice.Enter final ContextScope scope, @Advice.Thrown final Throwable throwable, - @Advice.Local("handlerSpanKey") String handlerSpanKey) { + @Advice.Local("handlerClass") Class handlerClass) { if (scope == null) { return; } boolean finish = - !Boolean.TRUE.equals( - request.getAttribute(handlerSpanKey + DD_HANDLER_SPAN_CONTINUE_SUFFIX)); + !Boolean.TRUE.equals(request.getAttribute(handlerSpanContinueKey(handlerClass))); final AgentSpan span = spanFromContext(scope.context()); scope.close(); if (throwable != null) { diff --git a/dd-java-agent/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/src/main/java/datadog/trace/instrumentation/springweb/InvocableHandlerMethodInstrumentation.java b/dd-java-agent/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/src/main/java/datadog/trace/instrumentation/springweb/InvocableHandlerMethodInstrumentation.java index a4b26041f02..01a25b7cbca 100644 --- a/dd-java-agent/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/src/main/java/datadog/trace/instrumentation/springweb/InvocableHandlerMethodInstrumentation.java +++ b/dd-java-agent/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/src/main/java/datadog/trace/instrumentation/springweb/InvocableHandlerMethodInstrumentation.java @@ -1,8 +1,8 @@ package datadog.trace.instrumentation.springweb; import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named; -import static datadog.trace.instrumentation.springweb.SpringWebHttpServerDecorator.DD_HANDLER_SPAN_CONTINUE_SUFFIX; -import static datadog.trace.instrumentation.springweb.SpringWebHttpServerDecorator.DD_HANDLER_SPAN_PREFIX_KEY; +import static datadog.trace.instrumentation.springweb.SpringWebHttpServerDecorator.handlerSpanContinueKey; +import static datadog.trace.instrumentation.springweb.SpringWebHttpServerDecorator.handlerSpanKey; import com.google.auto.service.AutoService; import datadog.trace.agent.tooling.Instrumenter; @@ -53,12 +53,12 @@ public static void after( return; } ServletWebRequest servletWebRequest = (ServletWebRequest) nativeWebRequest; - final String handlerSpanKey = - DD_HANDLER_SPAN_PREFIX_KEY + self.getBean().getClass().getName(); + final Class handlerClass = self.getBean().getClass(); + final String handlerSpanKey = handlerSpanKey(handlerClass); if (Boolean.TRUE.equals( servletWebRequest.getAttribute( - handlerSpanKey + DD_HANDLER_SPAN_CONTINUE_SUFFIX, ServletWebRequest.SCOPE_REQUEST))) { + handlerSpanContinueKey(handlerClass), ServletWebRequest.SCOPE_REQUEST))) { return; } @@ -67,7 +67,7 @@ public static void after( return; } servletWebRequest.setAttribute( - handlerSpanKey + DD_HANDLER_SPAN_CONTINUE_SUFFIX, true, ServletWebRequest.SCOPE_REQUEST); + handlerSpanContinueKey(handlerClass), true, ServletWebRequest.SCOPE_REQUEST); result = ((CompletionStage) result) .whenComplete(AsyncResultExtensions.finishSpan((AgentSpan) span)); diff --git a/dd-java-agent/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/src/main/java/datadog/trace/instrumentation/springweb/SpringWebHttpServerDecorator.java b/dd-java-agent/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/src/main/java/datadog/trace/instrumentation/springweb/SpringWebHttpServerDecorator.java index 52ce8855048..9d69a367211 100644 --- a/dd-java-agent/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/src/main/java/datadog/trace/instrumentation/springweb/SpringWebHttpServerDecorator.java +++ b/dd-java-agent/instrumentation/spring/spring-webmvc/spring-webmvc-3.1/src/main/java/datadog/trace/instrumentation/springweb/SpringWebHttpServerDecorator.java @@ -3,6 +3,7 @@ import static datadog.trace.bootstrap.instrumentation.decorator.http.HttpResourceDecorator.HTTP_RESOURCE_DECORATOR; import datadog.context.Context; +import datadog.trace.api.GenericClassValue; import datadog.trace.bootstrap.instrumentation.api.AgentPropagation; import datadog.trace.bootstrap.instrumentation.api.AgentSpan; import datadog.trace.bootstrap.instrumentation.api.URIDataAdapter; @@ -33,6 +34,21 @@ public class SpringWebHttpServerDecorator public static final String DD_HANDLER_SPAN_PREFIX_KEY = "dd.handler.span."; public static final String DD_HANDLER_SPAN_CONTINUE_SUFFIX = ".continue"; + private static final ClassValue HANDLER_SPAN_KEY_CACHE = + GenericClassValue.of(type -> DD_HANDLER_SPAN_PREFIX_KEY + type.getName()); + + private static final ClassValue HANDLER_SPAN_CONTINUE_KEY_CACHE = + GenericClassValue.of( + type -> DD_HANDLER_SPAN_PREFIX_KEY + type.getName() + DD_HANDLER_SPAN_CONTINUE_SUFFIX); + + public static String handlerSpanKey(Class handlerClass) { + return HANDLER_SPAN_KEY_CACHE.get(handlerClass); + } + + public static String handlerSpanContinueKey(Class handlerClass) { + return HANDLER_SPAN_CONTINUE_KEY_CACHE.get(handlerClass); + } + public SpringWebHttpServerDecorator(CharSequence component) { this.component = component; } diff --git a/dd-java-agent/instrumentation/spring/spring-webmvc/spring-webmvc-6.0/src/main/java17/datadog/trace/instrumentation/springweb6/ControllerAdvice.java b/dd-java-agent/instrumentation/spring/spring-webmvc/spring-webmvc-6.0/src/main/java17/datadog/trace/instrumentation/springweb6/ControllerAdvice.java index bb95ff99f8f..e85ec784bd7 100644 --- a/dd-java-agent/instrumentation/spring/spring-webmvc/spring-webmvc-6.0/src/main/java17/datadog/trace/instrumentation/springweb6/ControllerAdvice.java +++ b/dd-java-agent/instrumentation/spring/spring-webmvc/spring-webmvc-6.0/src/main/java17/datadog/trace/instrumentation/springweb6/ControllerAdvice.java @@ -6,9 +6,9 @@ import static datadog.trace.bootstrap.instrumentation.api.Java8BytecodeBridge.getCurrentContext; import static datadog.trace.bootstrap.instrumentation.api.Java8BytecodeBridge.spanFromContext; import static datadog.trace.bootstrap.instrumentation.decorator.HttpServerDecorator.DD_CONTEXT_ATTRIBUTE; -import static datadog.trace.instrumentation.springweb6.SpringWebHttpServerDecorator.DD_HANDLER_SPAN_CONTINUE_SUFFIX; -import static datadog.trace.instrumentation.springweb6.SpringWebHttpServerDecorator.DD_HANDLER_SPAN_PREFIX_KEY; import static datadog.trace.instrumentation.springweb6.SpringWebHttpServerDecorator.DECORATE; +import static datadog.trace.instrumentation.springweb6.SpringWebHttpServerDecorator.handlerSpanContinueKey; +import static datadog.trace.instrumentation.springweb6.SpringWebHttpServerDecorator.handlerSpanKey; import datadog.context.Context; import datadog.context.ContextScope; @@ -23,8 +23,8 @@ public class ControllerAdvice { public static ContextScope nameResourceAndStartSpan( @Advice.Argument(0) final HttpServletRequest request, @Advice.Argument(2) final Object handler, - @Advice.Local("handlerSpanKey") String handlerSpanKey) { - handlerSpanKey = ""; + @Advice.Local("handlerClass") Class handlerClass) { + handlerClass = null; // Name the parent span based on the matching pattern Object contextObj = request.getAttribute(DD_CONTEXT_ATTRIBUTE); if (contextObj instanceof Context) { @@ -41,13 +41,12 @@ public static ContextScope nameResourceAndStartSpan( // Now create a span for handler/controller execution. - final String handlerKey; if (handler instanceof HandlerMethod) { - handlerKey = ((HandlerMethod) handler).getBean().getClass().getName(); + handlerClass = ((HandlerMethod) handler).getBean().getClass(); } else { - handlerKey = handler.getClass().getName(); + handlerClass = handler.getClass(); } - handlerSpanKey = DD_HANDLER_SPAN_PREFIX_KEY + handlerKey; + final String handlerSpanKey = handlerSpanKey(handlerClass); // If the context already exists, return it final Object existingContext = request.getAttribute(handlerSpanKey); @@ -68,13 +67,12 @@ public static void stopSpan( @Advice.Enter final ContextScope scope, @Advice.Argument(0) final HttpServletRequest request, @Advice.Thrown final Throwable throwable, - @Advice.Local("handlerSpanKey") String handlerSpanKey) { + @Advice.Local("handlerClass") Class handlerClass) { if (scope == null) { return; } boolean finish = - !Boolean.TRUE.equals( - request.getAttribute(handlerSpanKey + DD_HANDLER_SPAN_CONTINUE_SUFFIX)); + !Boolean.TRUE.equals(request.getAttribute(handlerSpanContinueKey(handlerClass))); final AgentSpan span = spanFromContext(scope.context()); scope.close(); if (throwable != null) { diff --git a/dd-java-agent/instrumentation/spring/spring-webmvc/spring-webmvc-6.0/src/main/java17/datadog/trace/instrumentation/springweb6/SpringWebHttpServerDecorator.java b/dd-java-agent/instrumentation/spring/spring-webmvc/spring-webmvc-6.0/src/main/java17/datadog/trace/instrumentation/springweb6/SpringWebHttpServerDecorator.java index 79b89c1aad5..65129d27b74 100644 --- a/dd-java-agent/instrumentation/spring/spring-webmvc/spring-webmvc-6.0/src/main/java17/datadog/trace/instrumentation/springweb6/SpringWebHttpServerDecorator.java +++ b/dd-java-agent/instrumentation/spring/spring-webmvc/spring-webmvc-6.0/src/main/java17/datadog/trace/instrumentation/springweb6/SpringWebHttpServerDecorator.java @@ -3,6 +3,7 @@ import static datadog.trace.bootstrap.instrumentation.decorator.http.HttpResourceDecorator.HTTP_RESOURCE_DECORATOR; import datadog.context.Context; +import datadog.trace.api.GenericClassValue; import datadog.trace.bootstrap.instrumentation.api.AgentPropagation; import datadog.trace.bootstrap.instrumentation.api.AgentSpan; import datadog.trace.bootstrap.instrumentation.api.URIDataAdapter; @@ -37,6 +38,21 @@ public class SpringWebHttpServerDecorator public static final String DD_HANDLER_SPAN_PREFIX_KEY = "dd.handler.span."; public static final String DD_HANDLER_SPAN_CONTINUE_SUFFIX = ".continue"; + private static final ClassValue HANDLER_SPAN_KEY_CACHE = + GenericClassValue.of(type -> DD_HANDLER_SPAN_PREFIX_KEY + type.getName()); + + private static final ClassValue HANDLER_SPAN_CONTINUE_KEY_CACHE = + GenericClassValue.of( + type -> DD_HANDLER_SPAN_PREFIX_KEY + type.getName() + DD_HANDLER_SPAN_CONTINUE_SUFFIX); + + public static String handlerSpanKey(Class handlerClass) { + return HANDLER_SPAN_KEY_CACHE.get(handlerClass); + } + + public static String handlerSpanContinueKey(Class handlerClass) { + return HANDLER_SPAN_CONTINUE_KEY_CACHE.get(handlerClass); + } + public SpringWebHttpServerDecorator(CharSequence component) { this.component = component; } diff --git a/dd-java-agent/instrumentation/spring/spring-webmvc/spring-webmvc-6.0/src/main/java17/datadog/trace/instrumentation/springweb6/WrapContinuableResultAdvice.java b/dd-java-agent/instrumentation/spring/spring-webmvc/spring-webmvc-6.0/src/main/java17/datadog/trace/instrumentation/springweb6/WrapContinuableResultAdvice.java index 4939751da1b..dc3e5fbf0c6 100644 --- a/dd-java-agent/instrumentation/spring/spring-webmvc/spring-webmvc-6.0/src/main/java17/datadog/trace/instrumentation/springweb6/WrapContinuableResultAdvice.java +++ b/dd-java-agent/instrumentation/spring/spring-webmvc/spring-webmvc-6.0/src/main/java17/datadog/trace/instrumentation/springweb6/WrapContinuableResultAdvice.java @@ -1,7 +1,7 @@ package datadog.trace.instrumentation.springweb6; -import static datadog.trace.instrumentation.springweb6.SpringWebHttpServerDecorator.DD_HANDLER_SPAN_CONTINUE_SUFFIX; -import static datadog.trace.instrumentation.springweb6.SpringWebHttpServerDecorator.DD_HANDLER_SPAN_PREFIX_KEY; +import static datadog.trace.instrumentation.springweb6.SpringWebHttpServerDecorator.handlerSpanContinueKey; +import static datadog.trace.instrumentation.springweb6.SpringWebHttpServerDecorator.handlerSpanKey; import datadog.trace.bootstrap.instrumentation.api.AgentSpan; import datadog.trace.bootstrap.instrumentation.java.concurrent.AsyncResultExtensions; @@ -24,11 +24,12 @@ public static void after( } ServletWebRequest servletWebRequest = (ServletWebRequest) nativeWebRequest; - final String handlerSpanKey = DD_HANDLER_SPAN_PREFIX_KEY + self.getBean().getClass().getName(); + final Class handlerClass = self.getBean().getClass(); + final String handlerSpanKey = handlerSpanKey(handlerClass); if (Boolean.TRUE.equals( servletWebRequest.getAttribute( - handlerSpanKey + DD_HANDLER_SPAN_CONTINUE_SUFFIX, ServletWebRequest.SCOPE_REQUEST))) { + handlerSpanContinueKey(handlerClass), ServletWebRequest.SCOPE_REQUEST))) { return; } Object span = servletWebRequest.getAttribute(handlerSpanKey, ServletWebRequest.SCOPE_REQUEST); @@ -36,7 +37,7 @@ public static void after( return; } servletWebRequest.setAttribute( - handlerSpanKey + DD_HANDLER_SPAN_CONTINUE_SUFFIX, true, ServletWebRequest.SCOPE_REQUEST); + handlerSpanContinueKey(handlerClass), true, ServletWebRequest.SCOPE_REQUEST); result = ((CompletionStage) result) .whenComplete(AsyncResultExtensions.finishSpan((AgentSpan) span));