From 82925104f0c7d0eecc23a80d5f69ada6ea263634 Mon Sep 17 00:00:00 2001
From: Jonatan Ivanov <jonatan.ivanov@gmail.com>
Date: Fri, 29 Sep 2023 15:59:08 -0700
Subject: [PATCH] Add auto-configuration for SpanAspect

---
 .../MicrometerTracingAutoConfiguration.java   | 44 ++++++++-
 ...crometerTracingAutoConfigurationTests.java | 97 +++++++++++++++++--
 2 files changed, 130 insertions(+), 11 deletions(-)

diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/MicrometerTracingAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/MicrometerTracingAutoConfiguration.java
index 149141c887f9..40721dcbb90a 100644
--- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/MicrometerTracingAutoConfiguration.java
+++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/MicrometerTracingAutoConfiguration.java
@@ -17,17 +17,26 @@
 package org.springframework.boot.actuate.autoconfigure.tracing;
 
 import io.micrometer.tracing.Tracer;
+import io.micrometer.tracing.annotation.DefaultNewSpanParser;
+import io.micrometer.tracing.annotation.ImperativeMethodInvocationProcessor;
+import io.micrometer.tracing.annotation.MethodInvocationProcessor;
+import io.micrometer.tracing.annotation.NewSpanParser;
+import io.micrometer.tracing.annotation.SpanAspect;
+import io.micrometer.tracing.annotation.SpanTagAnnotationHandler;
 import io.micrometer.tracing.handler.DefaultTracingObservationHandler;
 import io.micrometer.tracing.handler.PropagatingReceiverTracingObservationHandler;
 import io.micrometer.tracing.handler.PropagatingSenderTracingObservationHandler;
 import io.micrometer.tracing.propagation.Propagator;
+import org.aspectj.weaver.Advice;
 
+import org.springframework.beans.factory.ObjectProvider;
 import org.springframework.boot.autoconfigure.AutoConfiguration;
 import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
 import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
 import org.springframework.core.Ordered;
 import org.springframework.core.annotation.Order;
 
@@ -35,10 +44,12 @@
  * {@link EnableAutoConfiguration Auto-configuration} for the Micrometer Tracing API.
  *
  * @author Moritz Halbritter
+ * @author Jonatan Ivanov
  * @since 3.0.0
  */
 @AutoConfiguration
 @ConditionalOnClass(Tracer.class)
+@ConditionalOnBean(Tracer.class)
 public class MicrometerTracingAutoConfiguration {
 
 	/**
@@ -60,7 +71,6 @@ public class MicrometerTracingAutoConfiguration {
 
 	@Bean
 	@ConditionalOnMissingBean
-	@ConditionalOnBean(Tracer.class)
 	@Order(DEFAULT_TRACING_OBSERVATION_HANDLER_ORDER)
 	public DefaultTracingObservationHandler defaultTracingObservationHandler(Tracer tracer) {
 		return new DefaultTracingObservationHandler(tracer);
@@ -68,7 +78,7 @@ public DefaultTracingObservationHandler defaultTracingObservationHandler(Tracer
 
 	@Bean
 	@ConditionalOnMissingBean
-	@ConditionalOnBean({ Tracer.class, Propagator.class })
+	@ConditionalOnBean(Propagator.class)
 	@Order(SENDER_TRACING_OBSERVATION_HANDLER_ORDER)
 	public PropagatingSenderTracingObservationHandler<?> propagatingSenderTracingObservationHandler(Tracer tracer,
 			Propagator propagator) {
@@ -77,11 +87,39 @@ public PropagatingSenderTracingObservationHandler<?> propagatingSenderTracingObs
 
 	@Bean
 	@ConditionalOnMissingBean
-	@ConditionalOnBean({ Tracer.class, Propagator.class })
+	@ConditionalOnBean(Propagator.class)
 	@Order(RECEIVER_TRACING_OBSERVATION_HANDLER_ORDER)
 	public PropagatingReceiverTracingObservationHandler<?> propagatingReceiverTracingObservationHandler(Tracer tracer,
 			Propagator propagator) {
 		return new PropagatingReceiverTracingObservationHandler<>(tracer, propagator);
 	}
 
+	@Configuration(proxyBeanMethods = false)
+	@ConditionalOnClass(Advice.class)
+	static class SpanAspectConfiguration {
+
+		@Bean
+		@ConditionalOnMissingBean
+		DefaultNewSpanParser newSpanParser() {
+			return new DefaultNewSpanParser();
+		}
+
+		@Bean
+		@ConditionalOnMissingBean
+		ImperativeMethodInvocationProcessor imperativeMethodInvocationProcessor(NewSpanParser newSpanParser,
+				Tracer tracer, ObjectProvider<SpanTagAnnotationHandler> spanTagAnnotationHandler) {
+			ImperativeMethodInvocationProcessor methodInvocationProcessor = new ImperativeMethodInvocationProcessor(
+					newSpanParser, tracer);
+			spanTagAnnotationHandler.ifAvailable(methodInvocationProcessor::setSpanTagAnnotationHandler);
+			return methodInvocationProcessor;
+		}
+
+		@Bean
+		@ConditionalOnMissingBean
+		SpanAspect spanAspect(MethodInvocationProcessor methodInvocationProcessor) {
+			return new SpanAspect(methodInvocationProcessor);
+		}
+
+	}
+
 }
diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/MicrometerTracingAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/MicrometerTracingAutoConfigurationTests.java
index d9f94f46396a..3ca8ff2364af 100644
--- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/MicrometerTracingAutoConfigurationTests.java
+++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/tracing/MicrometerTracingAutoConfigurationTests.java
@@ -19,18 +19,27 @@
 import java.util.List;
 
 import io.micrometer.tracing.Tracer;
+import io.micrometer.tracing.annotation.DefaultNewSpanParser;
+import io.micrometer.tracing.annotation.ImperativeMethodInvocationProcessor;
+import io.micrometer.tracing.annotation.MethodInvocationProcessor;
+import io.micrometer.tracing.annotation.NewSpanParser;
+import io.micrometer.tracing.annotation.SpanAspect;
+import io.micrometer.tracing.annotation.SpanTagAnnotationHandler;
 import io.micrometer.tracing.handler.DefaultTracingObservationHandler;
 import io.micrometer.tracing.handler.PropagatingReceiverTracingObservationHandler;
 import io.micrometer.tracing.handler.PropagatingSenderTracingObservationHandler;
 import io.micrometer.tracing.handler.TracingObservationHandler;
 import io.micrometer.tracing.propagation.Propagator;
+import org.aspectj.weaver.Advice;
 import org.junit.jupiter.api.Test;
 
 import org.springframework.boot.autoconfigure.AutoConfigurations;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
 import org.springframework.boot.test.context.FilteredClassLoader;
 import org.springframework.boot.test.context.runner.ApplicationContextRunner;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
+import org.springframework.test.util.ReflectionTestUtils;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
@@ -39,6 +48,7 @@
  * Tests for {@link MicrometerTracingAutoConfiguration}.
  *
  * @author Moritz Halbritter
+ * @author Jonatan Ivanov
  */
 class MicrometerTracingAutoConfigurationTests {
 
@@ -52,6 +62,9 @@ void shouldSupplyBeans() {
 				assertThat(context).hasSingleBean(DefaultTracingObservationHandler.class);
 				assertThat(context).hasSingleBean(PropagatingReceiverTracingObservationHandler.class);
 				assertThat(context).hasSingleBean(PropagatingSenderTracingObservationHandler.class);
+				assertThat(context).hasSingleBean(DefaultNewSpanParser.class);
+				assertThat(context).hasSingleBean(ImperativeMethodInvocationProcessor.class);
+				assertThat(context).hasSingleBean(SpanAspect.class);
 			});
 	}
 
@@ -75,14 +88,21 @@ void shouldSupplyBeansInCorrectOrder() {
 
 	@Test
 	void shouldBackOffOnCustomBeans() {
-		this.contextRunner.withUserConfiguration(CustomConfiguration.class).run((context) -> {
-			assertThat(context).hasBean("customDefaultTracingObservationHandler");
-			assertThat(context).hasSingleBean(DefaultTracingObservationHandler.class);
-			assertThat(context).hasBean("customPropagatingReceiverTracingObservationHandler");
-			assertThat(context).hasSingleBean(PropagatingReceiverTracingObservationHandler.class);
-			assertThat(context).hasBean("customPropagatingSenderTracingObservationHandler");
-			assertThat(context).hasSingleBean(PropagatingSenderTracingObservationHandler.class);
-		});
+		this.contextRunner.withUserConfiguration(TracerConfiguration.class, CustomConfiguration.class)
+			.run((context) -> {
+				assertThat(context).hasBean("customDefaultTracingObservationHandler");
+				assertThat(context).hasSingleBean(DefaultTracingObservationHandler.class);
+				assertThat(context).hasBean("customPropagatingReceiverTracingObservationHandler");
+				assertThat(context).hasSingleBean(PropagatingReceiverTracingObservationHandler.class);
+				assertThat(context).hasBean("customPropagatingSenderTracingObservationHandler");
+				assertThat(context).hasSingleBean(PropagatingSenderTracingObservationHandler.class);
+				assertThat(context).hasBean("customDefaultNewSpanParser");
+				assertThat(context).hasSingleBean(DefaultNewSpanParser.class);
+				assertThat(context).hasBean("customImperativeMethodInvocationProcessor");
+				assertThat(context).hasSingleBean(ImperativeMethodInvocationProcessor.class);
+				assertThat(context).hasBean("customSpanAspect");
+				assertThat(context).hasSingleBean(SpanAspect.class);
+			});
 	}
 
 	@Test
@@ -91,6 +111,9 @@ void shouldNotSupplyBeansIfMicrometerIsMissing() {
 			assertThat(context).doesNotHaveBean(DefaultTracingObservationHandler.class);
 			assertThat(context).doesNotHaveBean(PropagatingReceiverTracingObservationHandler.class);
 			assertThat(context).doesNotHaveBean(PropagatingSenderTracingObservationHandler.class);
+			assertThat(context).doesNotHaveBean(DefaultNewSpanParser.class);
+			assertThat(context).doesNotHaveBean(ImperativeMethodInvocationProcessor.class);
+			assertThat(context).doesNotHaveBean(SpanAspect.class);
 		});
 	}
 
@@ -100,17 +123,47 @@ void shouldNotSupplyBeansIfTracerIsMissing() {
 			assertThat(context).doesNotHaveBean(DefaultTracingObservationHandler.class);
 			assertThat(context).doesNotHaveBean(PropagatingReceiverTracingObservationHandler.class);
 			assertThat(context).doesNotHaveBean(PropagatingSenderTracingObservationHandler.class);
+			assertThat(context).doesNotHaveBean(DefaultNewSpanParser.class);
+			assertThat(context).doesNotHaveBean(ImperativeMethodInvocationProcessor.class);
+			assertThat(context).doesNotHaveBean(SpanAspect.class);
 		});
 	}
 
+	@Test
+	void shouldNotSupplyBeansIfAspectjIsMissing() {
+		this.contextRunner.withUserConfiguration(TracerConfiguration.class)
+			.withClassLoader(new FilteredClassLoader(Advice.class))
+			.run((context) -> {
+				assertThat(context).doesNotHaveBean(DefaultNewSpanParser.class);
+				assertThat(context).doesNotHaveBean(ImperativeMethodInvocationProcessor.class);
+				assertThat(context).doesNotHaveBean(SpanAspect.class);
+			});
+	}
+
 	@Test
 	void shouldNotSupplyBeansIfPropagatorIsMissing() {
 		this.contextRunner.withUserConfiguration(TracerConfiguration.class).run((context) -> {
 			assertThat(context).doesNotHaveBean(PropagatingSenderTracingObservationHandler.class);
 			assertThat(context).doesNotHaveBean(PropagatingReceiverTracingObservationHandler.class);
+
+			assertThat(context).hasSingleBean(DefaultNewSpanParser.class);
+			assertThat(context).hasSingleBean(ImperativeMethodInvocationProcessor.class);
+			assertThat(context).hasSingleBean(SpanAspect.class);
 		});
 	}
 
+	@Test
+	void shouldConfigureSpanTagAnnotationHandler() {
+		this.contextRunner.withUserConfiguration(TracerConfiguration.class, SpanTagAnnotationHandlerConfiguration.class)
+			.run((context) -> {
+				assertThat(context).hasSingleBean(DefaultNewSpanParser.class);
+				assertThat(context).hasSingleBean(SpanAspect.class);
+				assertThat(ReflectionTestUtils.getField(context.getBean(ImperativeMethodInvocationProcessor.class),
+						"spanTagAnnotationHandler"))
+					.isSameAs(context.getBean(SpanTagAnnotationHandler.class));
+			});
+	}
+
 	@Configuration(proxyBeanMethods = false)
 	private static class TracerConfiguration {
 
@@ -149,6 +202,34 @@ PropagatingSenderTracingObservationHandler<?> customPropagatingSenderTracingObse
 			return mock(PropagatingSenderTracingObservationHandler.class);
 		}
 
+		@Bean
+		DefaultNewSpanParser customDefaultNewSpanParser() {
+			return new DefaultNewSpanParser();
+		}
+
+		@Bean
+		@ConditionalOnMissingBean
+		ImperativeMethodInvocationProcessor customImperativeMethodInvocationProcessor(NewSpanParser newSpanParser,
+				Tracer tracer) {
+			return new ImperativeMethodInvocationProcessor(newSpanParser, tracer);
+		}
+
+		@Bean
+		@ConditionalOnMissingBean
+		SpanAspect customSpanAspect(MethodInvocationProcessor methodInvocationProcessor) {
+			return new SpanAspect(methodInvocationProcessor);
+		}
+
+	}
+
+	@Configuration(proxyBeanMethods = false)
+	private static class SpanTagAnnotationHandlerConfiguration {
+
+		@Bean
+		SpanTagAnnotationHandler spanTagAnnotationHandler() {
+			return new SpanTagAnnotationHandler((aClass) -> null, (aClass) -> null);
+		}
+
 	}
 
 }