From c5285164d6a1af4c3a852a66d304d3113381e515 Mon Sep 17 00:00:00 2001 From: Lenin Jaganathan Date: Fri, 5 Dec 2025 05:37:13 -0800 Subject: [PATCH] Fix RestClientBeanPostProcessor to avoid creating new bean when interceptor already present The RestClientBeanPostProcessor was always creating a new RestClient bean via mutate().build(), even when the OpenTelemetry interceptor was already present. This resulted in unnecessary bean creation. Fixes #15545 Signed-off-by: Lenin Jaganathan --- .../web/RestClientBeanPostProcessor.java | 33 +++++++++----- ...tInstrumentationAutoConfigurationTest.java | 44 +++++++++++++++++++ 2 files changed, 65 insertions(+), 12 deletions(-) diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring3/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/RestClientBeanPostProcessor.java b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring3/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/RestClientBeanPostProcessor.java index 9ef5b7f19194..c5cc05372142 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring3/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/RestClientBeanPostProcessor.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/main/javaSpring3/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/RestClientBeanPostProcessor.java @@ -10,6 +10,7 @@ import io.opentelemetry.instrumentation.spring.autoconfigure.internal.properties.InstrumentationConfigUtil; import io.opentelemetry.instrumentation.spring.web.v3_1.SpringWebTelemetry; import io.opentelemetry.instrumentation.spring.web.v3_1.internal.WebTelemetryUtil; +import java.util.concurrent.atomic.AtomicBoolean; import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.http.client.ClientHttpRequestInterceptor; @@ -40,18 +41,26 @@ private static RestClient addRestClientInterceptorIfNotPresent( RestClient restClient, OpenTelemetry openTelemetry, InstrumentationConfig config) { ClientHttpRequestInterceptor instrumentationInterceptor = getInterceptor(openTelemetry, config); - return restClient - .mutate() - .requestInterceptors( - interceptors -> { - if (interceptors.stream() - .noneMatch( - interceptor -> - interceptor.getClass() == instrumentationInterceptor.getClass())) { - interceptors.add(0, instrumentationInterceptor); - } - }) - .build(); + AtomicBoolean shouldAddInterceptor = new AtomicBoolean(false); + RestClient.Builder result = + restClient + .mutate() + .requestInterceptors( + interceptors -> { + if (isInterceptorNotPresent(interceptors, instrumentationInterceptor)) { + interceptors.add(0, instrumentationInterceptor); + shouldAddInterceptor.set(true); + } + }); + + return shouldAddInterceptor.get() ? result.build() : restClient; + } + + private static boolean isInterceptorNotPresent( + java.util.List interceptors, + ClientHttpRequestInterceptor instrumentationInterceptor) { + return interceptors.stream() + .noneMatch(interceptor -> interceptor.getClass() == instrumentationInterceptor.getClass()); } static ClientHttpRequestInterceptor getInterceptor( diff --git a/instrumentation/spring/spring-boot-autoconfigure/src/testSpring3/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/RestClientInstrumentationAutoConfigurationTest.java b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring3/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/RestClientInstrumentationAutoConfigurationTest.java index 3e16f15b23ef..63432ab62cc3 100644 --- a/instrumentation/spring/spring-boot-autoconfigure/src/testSpring3/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/RestClientInstrumentationAutoConfigurationTest.java +++ b/instrumentation/spring/spring-boot-autoconfigure/src/testSpring3/java/io/opentelemetry/instrumentation/spring/autoconfigure/internal/instrumentation/web/RestClientInstrumentationAutoConfigurationTest.java @@ -87,4 +87,48 @@ void defaultConfiguration() { "otelRestClientBeanPostProcessor", RestClientBeanPostProcessor.class)) .isNotNull()); } + + @Test + void shouldNotCreateNewBeanWhenInterceptorAlreadyPresent() { + contextRunner + .withPropertyValues("otel.instrumentation.spring-web.enabled=true") + .run( + context -> { + RestClientBeanPostProcessor beanPostProcessor = + context.getBean( + "otelRestClientBeanPostProcessor", RestClientBeanPostProcessor.class); + + RestClient restClientWithInterceptor = + RestClient.builder() + .requestInterceptor( + RestClientBeanPostProcessor.getInterceptor( + context.getBean(OpenTelemetry.class), + context.getBean(InstrumentationConfig.class))) + .build(); + + RestClient processed = + (RestClient) + beanPostProcessor.postProcessAfterInitialization( + restClientWithInterceptor, "testBean"); + + // Should return the same instance when interceptor is already present + assertThat(processed).isSameAs(restClientWithInterceptor); + + // Verify only one interceptor exists + processed + .mutate() + .requestInterceptors( + interceptors -> { + long count = + interceptors.stream() + .filter( + rti -> + rti.getClass() + .getName() + .startsWith("io.opentelemetry.instrumentation")) + .count(); + assertThat(count).isEqualTo(1); + }); + }); + } }