Skip to content

Commit d251321

Browse files
committed
Merge pull request #46007 from doumdoum
* gh-46007: Polish "Add @MeterTag support to existing @timed and @counted support" Add @MeterTag support to existing @timed and @counted support Closes gh-46007
2 parents a59cf66 + 88f4af7 commit d251321

File tree

8 files changed

+213
-45
lines changed

8 files changed

+213
-45
lines changed

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

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,38 +16,47 @@
1616

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

19+
import io.micrometer.common.annotation.ValueExpressionResolver;
1920
import io.micrometer.core.aop.CountedAspect;
21+
import io.micrometer.core.aop.CountedMeterTagAnnotationHandler;
2022
import io.micrometer.core.aop.MeterTagAnnotationHandler;
2123
import io.micrometer.core.aop.TimedAspect;
2224
import io.micrometer.core.instrument.MeterRegistry;
2325
import org.aspectj.weaver.Advice;
2426

27+
import org.springframework.beans.factory.BeanFactory;
2528
import org.springframework.beans.factory.ObjectProvider;
2629
import org.springframework.boot.autoconfigure.AutoConfiguration;
2730
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
2831
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
2932
import org.springframework.boot.autoconfigure.condition.ConditionalOnBooleanProperty;
3033
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
3134
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
35+
import org.springframework.boot.observation.autoconfigure.ObservationAutoConfiguration;
3236
import org.springframework.context.annotation.Bean;
3337

3438
/**
3539
* {@link EnableAutoConfiguration Auto-configuration} for Micrometer-based metrics
3640
* aspects.
3741
*
3842
* @author Jonatan Ivanov
43+
* @author Dominique Villard
3944
* @since 4.0.0
4045
*/
41-
@AutoConfiguration(after = { MetricsAutoConfiguration.class, CompositeMeterRegistryAutoConfiguration.class })
46+
@AutoConfiguration(after = { MetricsAutoConfiguration.class, CompositeMeterRegistryAutoConfiguration.class,
47+
ObservationAutoConfiguration.class })
4248
@ConditionalOnClass({ MeterRegistry.class, Advice.class })
4349
@ConditionalOnBooleanProperty("management.observations.annotations.enabled")
4450
@ConditionalOnBean(MeterRegistry.class)
4551
public class MetricsAspectsAutoConfiguration {
4652

4753
@Bean
4854
@ConditionalOnMissingBean
49-
CountedAspect countedAspect(MeterRegistry registry) {
50-
return new CountedAspect(registry);
55+
CountedAspect countedAspect(MeterRegistry registry,
56+
ObjectProvider<CountedMeterTagAnnotationHandler> countedMeterTagAnnotationHandler) {
57+
CountedAspect countedAspect = new CountedAspect(registry);
58+
countedMeterTagAnnotationHandler.ifAvailable(countedAspect::setMeterTagAnnotationHandler);
59+
return countedAspect;
5160
}
5261

5362
@Bean
@@ -59,4 +68,23 @@ TimedAspect timedAspect(MeterRegistry registry,
5968
return timedAspect;
6069
}
6170

71+
@ConditionalOnBean(ValueExpressionResolver.class)
72+
static class TagAnnotationHandlersConfiguration {
73+
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+
}
87+
88+
}
89+
6290
}

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

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

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

19+
import io.micrometer.common.annotation.ValueExpressionResolver;
1920
import io.micrometer.core.aop.CountedAspect;
21+
import io.micrometer.core.aop.CountedMeterTagAnnotationHandler;
2022
import io.micrometer.core.aop.MeterTagAnnotationHandler;
2123
import io.micrometer.core.aop.TimedAspect;
2224
import io.micrometer.core.instrument.MeterRegistry;
@@ -32,6 +34,7 @@
3234
import org.springframework.test.util.ReflectionTestUtils;
3335

3436
import static org.assertj.core.api.Assertions.assertThat;
37+
import static org.mockito.Mockito.mock;
3538

3639
/**
3740
* Tests for {@link MetricsAspectsAutoConfiguration}.
@@ -63,12 +66,53 @@ void shouldConfigureAspects() {
6366
}
6467

6568
@Test
66-
void shouldConfigureMeterTagAnnotationHandler() {
67-
this.contextRunner.withUserConfiguration(MeterTagAnnotationHandlerConfiguration.class).run((context) -> {
68-
assertThat(context).hasSingleBean(CountedAspect.class);
69-
assertThat(ReflectionTestUtils.getField(context.getBean(TimedAspect.class), "meterTagAnnotationHandler"))
70-
.isSameAs(context.getBean(MeterTagAnnotationHandler.class));
71-
});
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+
});
77+
}
78+
79+
@Test
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+
});
72116
}
73117

74118
@Test
@@ -120,14 +164,4 @@ TimedAspect customTimedAspect(MeterRegistry registry) {
120164

121165
}
122166

123-
@Configuration(proxyBeanMethods = false)
124-
static class MeterTagAnnotationHandlerConfiguration {
125-
126-
@Bean
127-
MeterTagAnnotationHandler meterTagAnnotationHandler() {
128-
return new MeterTagAnnotationHandler(null, null);
129-
}
130-
131-
}
132-
133167
}

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")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright 2012-present 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+
17+
package org.springframework.boot.observation.autoconfigure;
18+
19+
import io.micrometer.common.annotation.ValueExpressionResolver;
20+
21+
import org.springframework.expression.Expression;
22+
import org.springframework.expression.ExpressionParser;
23+
import org.springframework.expression.spel.standard.SpelExpressionParser;
24+
import org.springframework.expression.spel.support.SimpleEvaluationContext;
25+
26+
/**
27+
* A {@link Expression SpEL}-based {@link ValueExpressionResolver}.
28+
*
29+
* @author Dominique Villard
30+
*/
31+
class SpelValueExpressionResolver implements ValueExpressionResolver {
32+
33+
@Override
34+
public String resolve(String expression, Object parameter) {
35+
try {
36+
SimpleEvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build();
37+
ExpressionParser expressionParser = new SpelExpressionParser();
38+
Expression expressionToEvaluate = expressionParser.parseExpression(expression);
39+
return expressionToEvaluate.getValue(context, parameter, String.class);
40+
}
41+
catch (Exception ex) {
42+
throw new IllegalStateException("Unable to evaluate SpEL expression '%s'".formatted(expression), ex);
43+
}
44+
}
45+
46+
}

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

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright 2012-present 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+
17+
package org.springframework.boot.observation.autoconfigure;
18+
19+
import java.util.Map;
20+
21+
import org.junit.jupiter.api.Test;
22+
23+
import static org.assertj.core.api.Assertions.assertThat;
24+
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
25+
26+
/**
27+
* Tests for {@link SpelValueExpressionResolver}.
28+
*
29+
* @author Dominique Villard
30+
*/
31+
class SpelValueExpressionResolverTests {
32+
33+
final SpelValueExpressionResolver resolver = new SpelValueExpressionResolver();
34+
35+
@Test
36+
void checkValidExpression() {
37+
var value = Map.of("foo", Pair.of(1, 2));
38+
assertThat(this.resolver.resolve("['foo'].first", value)).isEqualTo("1");
39+
}
40+
41+
@Test
42+
void checkInvalidExpression() {
43+
var value = Map.of("foo", Pair.of(1, 2));
44+
assertThatIllegalStateException().isThrownBy(() -> this.resolver.resolve("['bar'].first", value));
45+
}
46+
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+
55+
}

0 commit comments

Comments
 (0)