Skip to content

Commit 8a82d55

Browse files
committed
GH-615 - Make sure we always place the ModuleEntryInterceptor after the AsyncAnnotationAdvisor.
To properly create spans for the actual method invocation, not the async dispatch.
1 parent 4c4ac0c commit 8a82d55

File tree

7 files changed

+140
-18
lines changed

7 files changed

+140
-18
lines changed

spring-modulith-observability/src/main/java/org/springframework/modulith/observability/ModuleTracingBeanPostProcessor.java

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,13 @@
1818
import io.micrometer.tracing.Tracer;
1919

2020
import java.lang.reflect.Method;
21+
import java.util.Arrays;
2122
import java.util.HashMap;
2223
import java.util.Map;
2324
import java.util.function.Supplier;
2425

2526
import org.springframework.aop.Advisor;
27+
import org.springframework.aop.framework.Advised;
2628
import org.springframework.aop.support.ComposablePointcut;
2729
import org.springframework.aop.support.DefaultPointcutAdvisor;
2830
import org.springframework.aop.support.StaticMethodMatcher;
@@ -81,19 +83,21 @@ public Object postProcessAfterInitialization(Object bean, String beanName) throw
8183
return bean;
8284
}
8385

86+
if (alreadyAdvised(bean)) {
87+
return bean;
88+
}
89+
8490
var modules = runtime.get();
8591

86-
return modules.getModuleByType(type.getName())
87-
.map(DefaultObservedModule::new)
88-
.map(it -> {
92+
return modules.getModuleByType(type.getName()).map(DefaultObservedModule::new).map(it -> {
8993

90-
var moduleType = it.getObservedModuleType(type, modules);
94+
var moduleType = it.getObservedModuleType(type, modules);
9195

92-
return moduleType != null //
93-
? addAdvisor(bean, getOrBuildAdvisor(it, moduleType)) //
94-
: bean;
96+
return moduleType != null //
97+
? addAdvisor(bean, getOrBuildAdvisor(it, moduleType)) //
98+
: bean;
9599

96-
}).orElse(bean);
100+
}).orElse(bean);
97101
}
98102

99103
private boolean isInfrastructureBean(String beanName) {
@@ -116,13 +120,15 @@ private boolean isInfrastructureBean(String beanName) {
116120
private Advisor getOrBuildAdvisor(ObservedModule module, ObservedModuleType type) {
117121

118122
return advisors.computeIfAbsent(module.getName(), __ -> {
123+
return new ApplicationModuleObservingAdvisor(type, ModuleEntryInterceptor.of(module, tracer.get()));
124+
});
125+
}
119126

120-
var interceptor = ModuleEntryInterceptor.of(module, tracer.get());
121-
var matcher = new ObservableTypeMethodMatcher(type);
122-
var pointcut = new ComposablePointcut(matcher);
127+
private static boolean alreadyAdvised(Object bean) {
123128

124-
return new DefaultPointcutAdvisor(pointcut, interceptor);
125-
});
129+
return bean instanceof Advised advised
130+
&& Arrays.stream(advised.getAdvisors())
131+
.anyMatch(ApplicationModuleObservingAdvisor.class::isInstance);
126132
}
127133

128134
private static class ObservableTypeMethodMatcher extends StaticMethodMatcher {
@@ -150,4 +156,13 @@ public boolean matches(Method method, Class<?> targetClass) {
150156
return type.getMethodsToIntercept().test(method);
151157
}
152158
}
159+
160+
static class ApplicationModuleObservingAdvisor extends DefaultPointcutAdvisor {
161+
162+
private static final long serialVersionUID = -391548409986032658L;
163+
164+
public ApplicationModuleObservingAdvisor(ObservedModuleType type, ModuleEntryInterceptor interceptor) {
165+
super(new ComposablePointcut(new ObservableTypeMethodMatcher(type)), interceptor);
166+
}
167+
}
153168
}

spring-modulith-observability/src/main/java/org/springframework/modulith/observability/ModuleTracingSupport.java

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import org.springframework.aop.framework.Advised;
2222
import org.springframework.aop.framework.ProxyFactory;
2323
import org.springframework.beans.factory.BeanClassLoaderAware;
24+
import org.springframework.scheduling.annotation.AsyncAnnotationAdvisor;
2425

2526
/**
2627
* @author Oliver Drotbohm
@@ -44,9 +45,10 @@ protected final Object addAdvisor(Object bean, Advisor advisor) {
4445

4546
protected final Object addAdvisor(Object bean, Advisor advisor, Consumer<ProxyFactory> customizer) {
4647

47-
if (Advised.class.isInstance(bean)) {
48+
if (bean instanceof Advised advised) {
49+
50+
advised.addAdvisor(asyncAdvisorIndex(advised) + 1, advisor);
4851

49-
((Advised) bean).addAdvisor(0, advisor);
5052
return bean;
5153

5254
} else {
@@ -58,4 +60,18 @@ protected final Object addAdvisor(Object bean, Advisor advisor, Consumer<ProxyFa
5860
return factory.getProxy(classLoader);
5961
}
6062
}
63+
64+
private static int asyncAdvisorIndex(Advised advised) {
65+
66+
Advisor[] advisors = advised.getAdvisors();
67+
68+
for (int i = 0; i < advised.getAdvisorCount(); i++) {
69+
70+
if (advisors[i] instanceof AsyncAnnotationAdvisor) {
71+
return i;
72+
}
73+
}
74+
75+
return -1;
76+
}
6177
}

spring-modulith-observability/src/test/java/example/ExampleApplication.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@
1717

1818
import org.springframework.boot.SpringApplication;
1919
import org.springframework.boot.autoconfigure.SpringBootApplication;
20+
import org.springframework.scheduling.annotation.EnableAsync;
2021

2122
/**
2223
* @author Oliver Drotbohm
2324
*/
25+
@EnableAsync
2426
@SpringBootApplication
2527
public class ExampleApplication {
2628

spring-modulith-observability/src/test/java/example/sample/SampleComponent.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package example.sample;
1717

18+
import org.springframework.scheduling.annotation.Async;
1819
import org.springframework.stereotype.Component;
1920

2021
/**
@@ -23,5 +24,6 @@
2324
@Component
2425
public class SampleComponent {
2526

26-
void someMethod() {}
27+
@Async
28+
public void someMethod() {}
2729
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
* Copyright 2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.modulith.observability;
17+
18+
import static org.assertj.core.api.Assertions.*;
19+
import static org.mockito.Mockito.*;
20+
21+
import example.ExampleApplication;
22+
import example.sample.SampleComponent;
23+
import io.micrometer.tracing.Tracer;
24+
25+
import org.junit.jupiter.api.Test;
26+
import org.springframework.aop.Advisor;
27+
import org.springframework.aop.framework.Advised;
28+
import org.springframework.boot.SpringApplication;
29+
import org.springframework.context.ConfigurableApplicationContext;
30+
import org.springframework.context.annotation.Bean;
31+
import org.springframework.context.annotation.Configuration;
32+
import org.springframework.modulith.observability.ModuleTracingBeanPostProcessor.ApplicationModuleObservingAdvisor;
33+
import org.springframework.modulith.runtime.ApplicationModulesRuntime;
34+
import org.springframework.modulith.runtime.ApplicationRuntime;
35+
import org.springframework.modulith.test.TestApplicationModules;
36+
import org.springframework.scheduling.annotation.AsyncAnnotationAdvisor;
37+
38+
/**
39+
* Integration tests for {@link ModuleTracingBeanPostProcessor}.
40+
*
41+
* @author Oliver Drotbohm
42+
*/
43+
class ModuleTracingBeanPostProcessorIntegrationTests {
44+
45+
@Test
46+
void decoratesExposedComponentsWithTracingInterceptor() {
47+
48+
SampleComponent bean = SpringApplication
49+
.run(new Class<?>[] { ExampleApplication.class, ModuleTracingConfiguration.class }, new String[] {})
50+
.getBean(SampleComponent.class);
51+
52+
assertThat(bean).isInstanceOfSatisfying(Advised.class, it -> {
53+
54+
var advisors = it.getAdvisors();
55+
56+
var asyncIndex = advisorIndex(AsyncAnnotationAdvisor.class, advisors);
57+
var tracingIndex = advisorIndex(ApplicationModuleObservingAdvisor.class, advisors);
58+
59+
assertThat(tracingIndex).isGreaterThan(asyncIndex);
60+
});
61+
}
62+
63+
@Configuration
64+
static class ModuleTracingConfiguration {
65+
66+
@Bean
67+
ModuleTracingBeanPostProcessor foo(ConfigurableApplicationContext context) {
68+
69+
var runtime = ApplicationRuntime.of(context);
70+
var modulesRuntime = new ApplicationModulesRuntime(() -> TestApplicationModules.of(ExampleApplication.class),
71+
runtime);
72+
73+
return new ModuleTracingBeanPostProcessor(modulesRuntime, () -> mock(Tracer.class), context.getBeanFactory());
74+
}
75+
}
76+
77+
private static int advisorIndex(Class<? extends Advisor> type, Advisor[] advisors) {
78+
79+
for (int i = 0; i < advisors.length; i++) {
80+
if (type.isInstance(advisors[i])) {
81+
return i;
82+
}
83+
}
84+
85+
throw new AssertionError("No advisor of type %s found!".formatted(type.getName()));
86+
}
87+
}

spring-modulith-observability/src/test/java/org/springframework/modulith/observability/ModuleTracingBeanPostProcessorUnitTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
*
3232
* @author Oliver Drotbohm
3333
*/
34-
public class ModuleTracingBeanPostProcessorUnitTests {
34+
class ModuleTracingBeanPostProcessorUnitTests {
3535

3636
@Test // GH-498
3737
void doesNotProxyConfiguationProperties() {

spring-modulith-observability/src/test/java/org/springframework/modulith/observability/ObservedModuleTypeUnitTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
*
3434
* @author Oliver Drotbohm
3535
*/
36-
public class ObservedModuleTypeUnitTests {
36+
class ObservedModuleTypeUnitTests {
3737

3838
static final ApplicationModules modules = TestApplicationModules.of("example");
3939

0 commit comments

Comments
 (0)