From 7807cffc1bef045c8674a70c80d04d12fcb8fa0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Nicoll?= Date: Mon, 14 Jul 2025 11:46:57 +0200 Subject: [PATCH] Guard ZipkinTracingAutoConfiguration against missing Encoding bean This commit harmonizes the check for the presence of an Encoding bean. Also, as this bean may be provided by another auto-configuration, this commit orders them explicitly. This means the dependency to spring-boot-zipkin has to elevate from a test-only dependency to (at least) an optional dependency to validate the links between the two auto-configurations. Closes gh-46295 --- module/spring-boot-tracing/build.gradle | 2 +- .../ZipkinTracingAutoConfiguration.java | 3 +- .../ZipkinTracingAutoConfigurationTests.java | 65 +++++++++++++++++++ 3 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 module/spring-boot-tracing/src/test/java/org/springframework/boot/tracing/autoconfigure/zipkin/ZipkinTracingAutoConfigurationTests.java diff --git a/module/spring-boot-tracing/build.gradle b/module/spring-boot-tracing/build.gradle index 3d888637f72..f7dba385a63 100644 --- a/module/spring-boot-tracing/build.gradle +++ b/module/spring-boot-tracing/build.gradle @@ -37,6 +37,7 @@ dependencies { optional(project(":core:spring-boot-testcontainers")) optional(project(":module:spring-boot-actuator-autoconfigure")) optional(project(":module:spring-boot-metrics")) + optional(project(":module:spring-boot-zipkin")) optional("io.micrometer:micrometer-core") optional("io.micrometer:micrometer-tracing-bridge-brave") optional("io.micrometer:micrometer-tracing-bridge-otel") @@ -55,7 +56,6 @@ dependencies { testImplementation(project(":core:spring-boot-test")) testImplementation(project(":module:spring-boot-opentelemetry")) testImplementation(project(":test-support:spring-boot-test-support")) - testImplementation(project(":module:spring-boot-zipkin")) testImplementation("com.squareup.okhttp3:mockwebserver") testImplementation("io.micrometer:micrometer-registry-prometheus") testImplementation("io.opentelemetry:opentelemetry-exporter-common") diff --git a/module/spring-boot-tracing/src/main/java/org/springframework/boot/tracing/autoconfigure/zipkin/ZipkinTracingAutoConfiguration.java b/module/spring-boot-tracing/src/main/java/org/springframework/boot/tracing/autoconfigure/zipkin/ZipkinTracingAutoConfiguration.java index 64fb16e0e02..d68e9ca0930 100644 --- a/module/spring-boot-tracing/src/main/java/org/springframework/boot/tracing/autoconfigure/zipkin/ZipkinTracingAutoConfiguration.java +++ b/module/spring-boot-tracing/src/main/java/org/springframework/boot/tracing/autoconfigure/zipkin/ZipkinTracingAutoConfiguration.java @@ -50,7 +50,7 @@ import org.springframework.context.annotation.Import; * @author Phillip Webb * @since 4.0.0 */ -@AutoConfiguration +@AutoConfiguration(afterName = "org.springframework.boot.zipkin.autoconfigure.ZipkinAutoConfiguration") @ConditionalOnClass(Encoding.class) @Import({ BraveConfiguration.class, OpenTelemetryConfiguration.class }) public class ZipkinTracingAutoConfiguration { @@ -60,6 +60,7 @@ public class ZipkinTracingAutoConfiguration { static class BraveConfiguration { @Bean + @ConditionalOnBean(Encoding.class) @ConditionalOnMissingBean(value = MutableSpan.class, parameterizedContainer = BytesEncoder.class) BytesEncoder mutableSpanBytesEncoder(Encoding encoding, ObjectProvider> throwableTagProvider) { diff --git a/module/spring-boot-tracing/src/test/java/org/springframework/boot/tracing/autoconfigure/zipkin/ZipkinTracingAutoConfigurationTests.java b/module/spring-boot-tracing/src/test/java/org/springframework/boot/tracing/autoconfigure/zipkin/ZipkinTracingAutoConfigurationTests.java new file mode 100644 index 00000000000..f373f54cb47 --- /dev/null +++ b/module/spring-boot-tracing/src/test/java/org/springframework/boot/tracing/autoconfigure/zipkin/ZipkinTracingAutoConfigurationTests.java @@ -0,0 +1,65 @@ +/* + * Copyright 2012-present the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.boot.tracing.autoconfigure.zipkin; + +import io.opentelemetry.exporter.zipkin.ZipkinSpanExporter; +import io.opentelemetry.sdk.trace.export.SpanExporter; +import org.junit.jupiter.api.Test; +import zipkin2.reporter.BytesEncoder; + +import org.springframework.boot.autoconfigure.AutoConfigurations; +import org.springframework.boot.test.context.runner.ApplicationContextRunner; +import org.springframework.boot.zipkin.autoconfigure.ZipkinAutoConfiguration; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link ZipkinTracingAutoConfiguration}. + * + * @author Stephane Nicoll + */ +class ZipkinTracingAutoConfigurationTests { + + private final ApplicationContextRunner contextRunner = new ApplicationContextRunner() + .withConfiguration(AutoConfigurations.of(ZipkinTracingAutoConfiguration.class)); + + @Test + void shouldNotSupplyBeansIfInfrastructureIsNotAvailable() { + this.contextRunner.run((context) -> assertThat(context).doesNotHaveBean(BytesEncoder.class) + .doesNotHaveBean(SpanExporter.class) + .doesNotHaveBean(ZipkinSpanExporter.class)); + } + + @Test + void shouldSupplyBeansIfInfrastructureIsAvailable() { + this.contextRunner.withConfiguration(AutoConfigurations.of(ZipkinAutoConfiguration.class)).run((context) -> { + assertThat(context).hasSingleBean(SpanExporter.class); + assertThat(context).hasSingleBean(ZipkinSpanExporter.class); + }); + } + + @Test + void shouldNotSupplyBeansIfTracingIsDisabled() { + this.contextRunner.withPropertyValues("management.tracing.enabled=false") + .withConfiguration(AutoConfigurations.of(ZipkinAutoConfiguration.class)) + .run((context) -> { + assertThat(context).doesNotHaveBean(SpanExporter.class); + assertThat(context).doesNotHaveBean(ZipkinSpanExporter.class); + }); + } + +}