Skip to content

Commit 88f4af7

Browse files
committed
Polish "Add @MeterTag support to existing @timed and @counted support"
See gh-46007
1 parent 8b04ace commit 88f4af7

File tree

8 files changed

+124
-83
lines changed

8 files changed

+124
-83
lines changed

spring-boot-project/spring-boot-metrics/src/main/java/org/springframework/boot/metrics/autoconfigure/MetricsAspectsAutoConfiguration.java

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.boot.metrics.autoconfigure;
1818

19+
import io.micrometer.common.annotation.ValueExpressionResolver;
1920
import io.micrometer.core.aop.CountedAspect;
2021
import io.micrometer.core.aop.CountedMeterTagAnnotationHandler;
2122
import io.micrometer.core.aop.MeterTagAnnotationHandler;
@@ -24,12 +25,14 @@
2425
import org.aspectj.weaver.Advice;
2526

2627
import org.springframework.beans.factory.BeanFactory;
28+
import org.springframework.beans.factory.ObjectProvider;
2729
import org.springframework.boot.autoconfigure.AutoConfiguration;
2830
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
2931
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
3032
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
3133
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
3234
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
35+
import org.springframework.boot.observation.autoconfigure.ObservationAutoConfiguration;
3336
import org.springframework.context.annotation.Bean;
3437

3538
/**
@@ -40,7 +43,8 @@
4043
* @author Dominique Villard
4144
* @since 4.0.0
4245
*/
43-
@AutoConfiguration(after = { MetricsAutoConfiguration.class, CompositeMeterRegistryAutoConfiguration.class })
46+
@AutoConfiguration(after = { MetricsAutoConfiguration.class, CompositeMeterRegistryAutoConfiguration.class,
47+
ObservationAutoConfiguration.class })
4448
@ConditionalOnClass({ MeterRegistry.class, Advice.class })
4549
@ConditionalOnBooleanProperty("management.observations.annotations.enabled")
4650
@ConditionalOnBean(MeterRegistry.class)
@@ -49,39 +53,38 @@ public class MetricsAspectsAutoConfiguration {
4953
@Bean
5054
@ConditionalOnMissingBean
5155
CountedAspect countedAspect(MeterRegistry registry,
52-
CountedMeterTagAnnotationHandler countedMeterTagAnnotationHandler) {
56+
ObjectProvider<CountedMeterTagAnnotationHandler> countedMeterTagAnnotationHandler) {
5357
CountedAspect countedAspect = new CountedAspect(registry);
54-
countedAspect.setMeterTagAnnotationHandler(countedMeterTagAnnotationHandler);
58+
countedMeterTagAnnotationHandler.ifAvailable(countedAspect::setMeterTagAnnotationHandler);
5559
return countedAspect;
5660
}
5761

5862
@Bean
5963
@ConditionalOnMissingBean
60-
TimedAspect timedAspect(MeterRegistry registry, MeterTagAnnotationHandler meterTagAnnotationHandler) {
64+
TimedAspect timedAspect(MeterRegistry registry,
65+
ObjectProvider<MeterTagAnnotationHandler> meterTagAnnotationHandler) {
6166
TimedAspect timedAspect = new TimedAspect(registry);
62-
timedAspect.setMeterTagAnnotationHandler(meterTagAnnotationHandler);
67+
meterTagAnnotationHandler.ifAvailable(timedAspect::setMeterTagAnnotationHandler);
6368
return timedAspect;
6469
}
6570

66-
@Bean
67-
@ConditionalOnMissingBean
68-
CountedMeterTagAnnotationHandler countedMeterTagAnnotationHandler(BeanFactory beanFactory,
69-
SpelTagValueExpressionResolver metricsTagValueExpressionResolver) {
70-
return new CountedMeterTagAnnotationHandler(beanFactory::getBean,
71-
(ignored) -> metricsTagValueExpressionResolver);
72-
}
71+
@ConditionalOnBean(ValueExpressionResolver.class)
72+
static class TagAnnotationHandlersConfiguration {
7373

74-
@Bean
75-
@ConditionalOnMissingBean
76-
MeterTagAnnotationHandler meterTagAnnotationHandler(BeanFactory beanFactory,
77-
SpelTagValueExpressionResolver meterTagValueExpressionResolver) {
78-
return new MeterTagAnnotationHandler(beanFactory::getBean, (ignored) -> meterTagValueExpressionResolver);
79-
}
74+
@Bean
75+
@ConditionalOnMissingBean
76+
CountedMeterTagAnnotationHandler countedMeterTagAnnotationHandler(BeanFactory beanFactory,
77+
ValueExpressionResolver valueExpressionResolver) {
78+
return new CountedMeterTagAnnotationHandler(beanFactory::getBean, (ignored) -> valueExpressionResolver);
79+
}
80+
81+
@Bean
82+
@ConditionalOnMissingBean
83+
MeterTagAnnotationHandler meterTagAnnotationHandler(BeanFactory beanFactory,
84+
ValueExpressionResolver valueExpressionResolver) {
85+
return new MeterTagAnnotationHandler(beanFactory::getBean, (ignored) -> valueExpressionResolver);
86+
}
8087

81-
@Bean
82-
@ConditionalOnMissingBean
83-
SpelTagValueExpressionResolver meterTagValueExpressionResolver() {
84-
return new SpelTagValueExpressionResolver();
8588
}
8689

8790
}

spring-boot-project/spring-boot-metrics/src/test/java/org/springframework/boot/metrics/autoconfigure/MetricsAspectsAutoConfigurationTests.java

Lines changed: 46 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.boot.metrics.autoconfigure;
1818

19+
import io.micrometer.common.annotation.ValueExpressionResolver;
1920
import io.micrometer.core.aop.CountedAspect;
2021
import io.micrometer.core.aop.CountedMeterTagAnnotationHandler;
2122
import io.micrometer.core.aop.MeterTagAnnotationHandler;
@@ -33,6 +34,7 @@
3334
import org.springframework.test.util.ReflectionTestUtils;
3435

3536
import static org.assertj.core.api.Assertions.assertThat;
37+
import static org.mockito.Mockito.mock;
3638

3739
/**
3840
* Tests for {@link MetricsAspectsAutoConfiguration}.
@@ -64,21 +66,53 @@ void shouldConfigureAspects() {
6466
}
6567

6668
@Test
67-
void shouldConfigureMeterTagAnnotationHandler() {
68-
this.contextRunner.withUserConfiguration(MeterTagAnnotationHandlerConfiguration.class).run((context) -> {
69-
assertThat(context).hasSingleBean(TimedAspect.class);
70-
assertThat(ReflectionTestUtils.getField(context.getBean(TimedAspect.class), "meterTagAnnotationHandler"))
71-
.isSameAs(context.getBean(MeterTagAnnotationHandler.class));
72-
});
69+
void shouldAutoConfigureMeterTagAnnotationHandlerWhenValueExpressionResolverIsAvailable() {
70+
this.contextRunner.withBean(ValueExpressionResolver.class, () -> mock(ValueExpressionResolver.class))
71+
.run((context) -> {
72+
assertThat(context).hasSingleBean(TimedAspect.class).hasSingleBean(MeterTagAnnotationHandler.class);
73+
assertThat(
74+
ReflectionTestUtils.getField(context.getBean(TimedAspect.class), "meterTagAnnotationHandler"))
75+
.isSameAs(context.getBean(MeterTagAnnotationHandler.class));
76+
});
7377
}
7478

7579
@Test
76-
void shouldConfigureCounterMeterTagAnnotationHandler() {
77-
this.contextRunner.withUserConfiguration(MeterTagAnnotationHandlerConfiguration.class).run((context) -> {
78-
assertThat(context).hasSingleBean(CountedAspect.class);
79-
assertThat(ReflectionTestUtils.getField(context.getBean(CountedAspect.class), "meterTagAnnotationHandler"))
80-
.isSameAs(context.getBean(CountedMeterTagAnnotationHandler.class));
81-
});
80+
void shouldUseUserDefinedMeterTagAnnotationHandler() {
81+
this.contextRunner
82+
.withBean("customMeterTagAnnotationHandler", MeterTagAnnotationHandler.class,
83+
() -> new MeterTagAnnotationHandler(null, null))
84+
.run((context) -> {
85+
assertThat(context).hasSingleBean(TimedAspect.class).hasSingleBean(MeterTagAnnotationHandler.class);
86+
assertThat(
87+
ReflectionTestUtils.getField(context.getBean(TimedAspect.class), "meterTagAnnotationHandler"))
88+
.isSameAs(context.getBean("customMeterTagAnnotationHandler"));
89+
});
90+
}
91+
92+
@Test
93+
void shouldAutoConfigureCountedMeterTagAnnotationHandlerWhenValueExpressionResolverIsAvailable() {
94+
this.contextRunner.withBean(ValueExpressionResolver.class, () -> mock(ValueExpressionResolver.class))
95+
.run((context) -> {
96+
assertThat(context).hasSingleBean(CountedAspect.class)
97+
.hasSingleBean(CountedMeterTagAnnotationHandler.class);
98+
assertThat(
99+
ReflectionTestUtils.getField(context.getBean(CountedAspect.class), "meterTagAnnotationHandler"))
100+
.isSameAs(context.getBean(CountedMeterTagAnnotationHandler.class));
101+
});
102+
}
103+
104+
@Test
105+
void shouldUseUserDefinedCountedMeterTagAnnotationHandler() {
106+
this.contextRunner
107+
.withBean("customCountedMeterTagAnnotationHandler", CountedMeterTagAnnotationHandler.class,
108+
() -> new CountedMeterTagAnnotationHandler(null, null))
109+
.run((context) -> {
110+
assertThat(context).hasSingleBean(CountedAspect.class)
111+
.hasSingleBean(CountedMeterTagAnnotationHandler.class);
112+
assertThat(
113+
ReflectionTestUtils.getField(context.getBean(CountedAspect.class), "meterTagAnnotationHandler"))
114+
.isSameAs(context.getBean(CountedMeterTagAnnotationHandler.class));
115+
});
82116
}
83117

84118
@Test
@@ -130,19 +164,4 @@ TimedAspect customTimedAspect(MeterRegistry registry) {
130164

131165
}
132166

133-
@Configuration(proxyBeanMethods = false)
134-
static class MeterTagAnnotationHandlerConfiguration {
135-
136-
@Bean
137-
MeterTagAnnotationHandler meterTagAnnotationHandler() {
138-
return new MeterTagAnnotationHandler(null, null);
139-
}
140-
141-
@Bean
142-
CountedMeterTagAnnotationHandler countedMeterTagAnnotationHandler() {
143-
return new CountedMeterTagAnnotationHandler(null, null);
144-
}
145-
146-
}
147-
148167
}

spring-boot-project/spring-boot-observation/src/main/java/org/springframework/boot/observation/autoconfigure/ObservationAutoConfiguration.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.boot.observation.autoconfigure;
1818

19+
import io.micrometer.common.annotation.ValueExpressionResolver;
1920
import io.micrometer.observation.GlobalObservationConvention;
2021
import io.micrometer.observation.ObservationFilter;
2122
import io.micrometer.observation.ObservationHandler;
@@ -73,6 +74,12 @@ PropertiesObservationFilterPredicate propertiesObservationFilter(ObservationProp
7374
return new PropertiesObservationFilterPredicate(properties);
7475
}
7576

77+
@Bean
78+
@ConditionalOnMissingBean(ValueExpressionResolver.class)
79+
SpelValueExpressionResolver spelValueExpressionResolver() {
80+
return new SpelValueExpressionResolver();
81+
}
82+
7683
@Configuration(proxyBeanMethods = false)
7784
@ConditionalOnClass(Advice.class)
7885
@ConditionalOnBooleanProperty("management.observations.annotations.enabled")
Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2025 the original author or authors.
2+
* Copyright 2012-present the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -14,25 +14,21 @@
1414
* limitations under the License.
1515
*/
1616

17-
package org.springframework.boot.metrics.autoconfigure;
17+
package org.springframework.boot.observation.autoconfigure;
1818

1919
import io.micrometer.common.annotation.ValueExpressionResolver;
20-
import io.micrometer.core.aop.MeterTag;
21-
import io.micrometer.tracing.annotation.SpanTag;
2220

2321
import org.springframework.expression.Expression;
2422
import org.springframework.expression.ExpressionParser;
2523
import org.springframework.expression.spel.standard.SpelExpressionParser;
2624
import org.springframework.expression.spel.support.SimpleEvaluationContext;
2725

2826
/**
29-
* Evaluates a Spel expression applied to a parameter for use in {@link MeterTag}
30-
* {@link SpanTag} Micrometer annotations.
27+
* A {@link Expression SpEL}-based {@link ValueExpressionResolver}.
3128
*
3229
* @author Dominique Villard
33-
* @since 4.0.0
3430
*/
35-
public class SpelTagValueExpressionResolver implements ValueExpressionResolver {
31+
class SpelValueExpressionResolver implements ValueExpressionResolver {
3632

3733
@Override
3834
public String resolve(String expression, Object parameter) {

spring-boot-project/spring-boot-observation/src/test/java/org/springframework/boot/observation/autoconfigure/ObservationAutoConfigurationTests.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import io.micrometer.common.KeyValue;
2020
import io.micrometer.common.KeyValues;
21+
import io.micrometer.common.annotation.ValueExpressionResolver;
2122
import io.micrometer.observation.GlobalObservationConvention;
2223
import io.micrometer.observation.Observation;
2324
import io.micrometer.observation.Observation.Context;
@@ -39,6 +40,7 @@
3940
import org.springframework.core.annotation.Order;
4041

4142
import static org.assertj.core.api.Assertions.assertThat;
43+
import static org.mockito.Mockito.mock;
4244

4345
/**
4446
* Tests for {@link ObservationAutoConfiguration}.
@@ -163,6 +165,18 @@ void shouldDisableSpringSecurityObservationsIfPropertyIsSet() {
163165
});
164166
}
165167

168+
@Test
169+
void autoConfiguresValueExpressionResolver() {
170+
this.contextRunner.run((context) -> assertThat(context).hasSingleBean(SpelValueExpressionResolver.class));
171+
}
172+
173+
@Test
174+
void allowsUserDefinedValueExpressionResolver() {
175+
this.contextRunner.withBean(ValueExpressionResolver.class, () -> mock(ValueExpressionResolver.class))
176+
.run((context) -> assertThat(context).hasSingleBean(ValueExpressionResolver.class)
177+
.doesNotHaveBean(SpelValueExpressionResolver.class));
178+
}
179+
166180
@Configuration(proxyBeanMethods = false)
167181
static class ObservationPredicates {
168182

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2025 the original author or authors.
2+
* Copyright 2012-present the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -14,20 +14,23 @@
1414
* limitations under the License.
1515
*/
1616

17-
package org.springframework.boot.metrics.autoconfigure;
17+
package org.springframework.boot.observation.autoconfigure;
1818

1919
import java.util.Map;
2020

2121
import org.junit.jupiter.api.Test;
2222

23-
import org.springframework.data.util.Pair;
24-
2523
import static org.assertj.core.api.Assertions.assertThat;
2624
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
2725

28-
class SpelTagValueExpressionResolverTests {
26+
/**
27+
* Tests for {@link SpelValueExpressionResolver}.
28+
*
29+
* @author Dominique Villard
30+
*/
31+
class SpelValueExpressionResolverTests {
2932

30-
final SpelTagValueExpressionResolver resolver = new SpelTagValueExpressionResolver();
33+
final SpelValueExpressionResolver resolver = new SpelValueExpressionResolver();
3134

3235
@Test
3336
void checkValidExpression() {
@@ -41,4 +44,12 @@ void checkInvalidExpression() {
4144
assertThatIllegalStateException().isThrownBy(() -> this.resolver.resolve("['bar'].first", value));
4245
}
4346

47+
record Pair(int first, int second) {
48+
49+
static Pair of(int first, int second) {
50+
return new Pair(first, second);
51+
}
52+
53+
}
54+
4455
}

spring-boot-project/spring-boot-tracing/src/main/java/org/springframework/boot/tracing/autoconfigure/MicrometerTracingAutoConfiguration.java

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.boot.tracing.autoconfigure;
1818

19+
import io.micrometer.common.annotation.ValueExpressionResolver;
1920
import io.micrometer.tracing.Tracer;
2021
import io.micrometer.tracing.annotation.DefaultNewSpanParser;
2122
import io.micrometer.tracing.annotation.ImperativeMethodInvocationProcessor;
@@ -31,13 +32,14 @@
3132
import org.aspectj.weaver.Advice;
3233

3334
import org.springframework.beans.factory.BeanFactory;
35+
import org.springframework.beans.factory.ObjectProvider;
3436
import org.springframework.boot.autoconfigure.AutoConfiguration;
3537
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
3638
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
3739
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
3840
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
3941
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
40-
import org.springframework.boot.metrics.autoconfigure.SpelTagValueExpressionResolver;
42+
import org.springframework.boot.observation.autoconfigure.ObservationAutoConfiguration;
4143
import org.springframework.boot.observation.autoconfigure.ObservationHandlerGroup;
4244
import org.springframework.context.annotation.Bean;
4345
import org.springframework.context.annotation.Configuration;
@@ -52,7 +54,7 @@
5254
* @author Jonatan Ivanov
5355
* @since 4.0.0
5456
*/
55-
@AutoConfiguration
57+
@AutoConfiguration(after = ObservationAutoConfiguration.class)
5658
@ConditionalOnBean(Tracer.class)
5759
public class MicrometerTracingAutoConfiguration {
5860

@@ -118,22 +120,18 @@ DefaultNewSpanParser newSpanParser() {
118120

119121
@Bean
120122
@ConditionalOnMissingBean
121-
SpelTagValueExpressionResolver spanTagValueExpressionResolver() {
122-
return new SpelTagValueExpressionResolver();
123-
}
124-
125-
@Bean
126-
@ConditionalOnMissingBean
123+
@ConditionalOnBean(ValueExpressionResolver.class)
127124
SpanTagAnnotationHandler spanTagAnnotationHandler(BeanFactory beanFactory,
128-
SpelTagValueExpressionResolver spanTagValueExpressionResolver) {
129-
return new SpanTagAnnotationHandler(beanFactory::getBean, (ignored) -> spanTagValueExpressionResolver);
125+
ValueExpressionResolver valueExpressionResolver) {
126+
return new SpanTagAnnotationHandler(beanFactory::getBean, (ignored) -> valueExpressionResolver);
130127
}
131128

132129
@Bean
133130
@ConditionalOnMissingBean(MethodInvocationProcessor.class)
134131
ImperativeMethodInvocationProcessor imperativeMethodInvocationProcessor(NewSpanParser newSpanParser,
135-
Tracer tracer, SpanTagAnnotationHandler spanTagAnnotationHandler) {
136-
return new ImperativeMethodInvocationProcessor(newSpanParser, tracer, spanTagAnnotationHandler);
132+
Tracer tracer, ObjectProvider<SpanTagAnnotationHandler> spanTagAnnotationHandler) {
133+
return new ImperativeMethodInvocationProcessor(newSpanParser, tracer,
134+
spanTagAnnotationHandler.getIfAvailable());
137135
}
138136

139137
@Bean

0 commit comments

Comments
 (0)