From 127b3a0013b066b07d2c34c398ef8c66ac89b527 Mon Sep 17 00:00:00 2001 From: Dominique Villard Date: Wed, 18 Jun 2025 11:02:30 +0200 Subject: [PATCH 1/7] add meterTags to counted aspects. Signed-off-by: Dominique Villard --- .../metrics/MetricsAspectsAutoConfiguration.java | 7 +++++-- .../MetricsAspectsAutoConfigurationTests.java | 14 ++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsAspectsAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsAspectsAutoConfiguration.java index 0c680cd7e234..e6e5696887d6 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsAspectsAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsAspectsAutoConfiguration.java @@ -17,6 +17,7 @@ package org.springframework.boot.actuate.autoconfigure.metrics; import io.micrometer.core.aop.CountedAspect; +import io.micrometer.core.aop.CountedMeterTagAnnotationHandler; import io.micrometer.core.aop.MeterTagAnnotationHandler; import io.micrometer.core.aop.TimedAspect; import io.micrometer.core.instrument.MeterRegistry; @@ -46,8 +47,10 @@ public class MetricsAspectsAutoConfiguration { @Bean @ConditionalOnMissingBean - CountedAspect countedAspect(MeterRegistry registry) { - return new CountedAspect(registry); + CountedAspect countedAspect(MeterRegistry registry, ObjectProvider countedMeterTagAnnotationHandler) { + CountedAspect countedAspect = new CountedAspect(registry); + countedMeterTagAnnotationHandler.ifAvailable(countedAspect::setMeterTagAnnotationHandler); + return countedAspect; } @Bean diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsAspectsAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsAspectsAutoConfigurationTests.java index d01bc5c2e479..f2912fd395dc 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsAspectsAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsAspectsAutoConfigurationTests.java @@ -17,6 +17,7 @@ package org.springframework.boot.actuate.autoconfigure.metrics; import io.micrometer.core.aop.CountedAspect; +import io.micrometer.core.aop.CountedMeterTagAnnotationHandler; import io.micrometer.core.aop.MeterTagAnnotationHandler; import io.micrometer.core.aop.TimedAspect; import io.micrometer.core.instrument.MeterRegistry; @@ -71,6 +72,15 @@ void shouldConfigureMeterTagAnnotationHandler() { }); } + @Test + void shouldConfigureCounterMeterTagAnnotationHandler() { + this.contextRunner.withUserConfiguration(MeterTagAnnotationHandlerConfiguration.class).run((context) -> { + assertThat(context).hasSingleBean(CountedAspect.class); + assertThat(ReflectionTestUtils.getField(context.getBean(CountedAspect.class), "countedMeterTagAnnotationHandler")) + .isSameAs(context.getBean(CountedMeterTagAnnotationHandler.class)); + }); + } + @Test void shouldNotConfigureAspectsIfMicrometerIsMissing() { this.contextRunner.withClassLoader(new FilteredClassLoader(MeterRegistry.class)).run((context) -> { @@ -128,6 +138,10 @@ MeterTagAnnotationHandler meterTagAnnotationHandler() { return new MeterTagAnnotationHandler(null, null); } + @Bean + CountedMeterTagAnnotationHandler countedMeterTagAnnotationHandler() { + return new CountedMeterTagAnnotationHandler(null, null); + } } } From c0e97fe444d133a476cad3ee104593e9cb2649c3 Mon Sep 17 00:00:00 2001 From: Dominique Villard Date: Fri, 20 Jun 2025 18:40:23 +0200 Subject: [PATCH 2/7] Make SpelTagValueExpressionResolver available for metrics and rework it so that it parses expressions only once Signed-off-by: Dominique Villard --- .../MicrometerTracingAutoConfiguration.java | 34 ++------ .../SpelTagValueExpressionResolver.java | 47 +++++++++++ .../SpelTagValueExpressionResolverTest.java | 81 +++++++++++++++++++ ...crometerTracingAutoConfigurationTests.java | 6 ++ 4 files changed, 142 insertions(+), 26 deletions(-) create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/SpelTagValueExpressionResolver.java create mode 100644 spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/SpelTagValueExpressionResolverTest.java 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 748f1f45bb80..869a92ef2559 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 @@ -16,7 +16,6 @@ package org.springframework.boot.actuate.autoconfigure.tracing; -import io.micrometer.common.annotation.ValueExpressionResolver; import io.micrometer.tracing.Tracer; import io.micrometer.tracing.annotation.DefaultNewSpanParser; import io.micrometer.tracing.annotation.ImperativeMethodInvocationProcessor; @@ -41,10 +40,6 @@ import org.springframework.context.annotation.Configuration; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; -import org.springframework.expression.Expression; -import org.springframework.expression.ExpressionParser; -import org.springframework.expression.spel.standard.SpelExpressionParser; -import org.springframework.expression.spel.support.SimpleEvaluationContext; /** * {@link EnableAutoConfiguration Auto-configuration} for the Micrometer Tracing API. @@ -113,9 +108,14 @@ DefaultNewSpanParser newSpanParser() { @Bean @ConditionalOnMissingBean - SpanTagAnnotationHandler spanTagAnnotationHandler(BeanFactory beanFactory) { - ValueExpressionResolver valueExpressionResolver = new SpelTagValueExpressionResolver(); - return new SpanTagAnnotationHandler(beanFactory::getBean, (ignored) -> valueExpressionResolver); + SpelTagValueExpressionResolver spanTagValueExpressionResolver() { + return new SpelTagValueExpressionResolver(); + } + + @Bean + @ConditionalOnMissingBean + SpanTagAnnotationHandler spanTagAnnotationHandler(BeanFactory beanFactory, SpelTagValueExpressionResolver spanTagValueExpressionResolver) { + return new SpanTagAnnotationHandler(beanFactory::getBean, (ignored) -> spanTagValueExpressionResolver); } @Bean @@ -132,22 +132,4 @@ SpanAspect spanAspect(MethodInvocationProcessor methodInvocationProcessor) { } } - - private static final class SpelTagValueExpressionResolver implements ValueExpressionResolver { - - @Override - public String resolve(String expression, Object parameter) { - try { - SimpleEvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build(); - ExpressionParser expressionParser = new SpelExpressionParser(); - Expression expressionToEvaluate = expressionParser.parseExpression(expression); - return expressionToEvaluate.getValue(context, parameter, String.class); - } - catch (Exception ex) { - throw new IllegalStateException("Unable to evaluate SpEL expression '%s'".formatted(expression), ex); - } - } - - } - } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/SpelTagValueExpressionResolver.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/SpelTagValueExpressionResolver.java new file mode 100644 index 000000000000..96fd7b7d632e --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/SpelTagValueExpressionResolver.java @@ -0,0 +1,47 @@ +/* + * Copyright 2012-2025 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.actuate.autoconfigure.tracing; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import io.micrometer.common.annotation.ValueExpressionResolver; + +import org.springframework.expression.Expression; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.SimpleEvaluationContext; + +public class SpelTagValueExpressionResolver implements ValueExpressionResolver { + + private final Map expressionMap = new ConcurrentHashMap<>(); + + @Override + public String resolve(String expression, Object parameter) { + try { + Expression expressionValue = this.expressionMap.computeIfAbsent(expression, SpelTagValueExpressionResolver::parseExpression); + SimpleEvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build(); + return expressionValue.getValue(context, parameter, String.class); + } + catch (Exception ex) { + throw new IllegalStateException("Unable to evaluate SpEL expression '%s'".formatted(expression), ex); + } + } + + private static Expression parseExpression(String expression) { + return new SpelExpressionParser().parseExpression(expression); + } +} diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/SpelTagValueExpressionResolverTest.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/SpelTagValueExpressionResolverTest.java new file mode 100644 index 000000000000..85037b36e6e9 --- /dev/null +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/SpelTagValueExpressionResolverTest.java @@ -0,0 +1,81 @@ +/* + * Copyright 2012-2025 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.actuate.autoconfigure.metrics; + +import java.util.Map; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import org.springframework.boot.actuate.autoconfigure.tracing.SpelTagValueExpressionResolver; +import org.springframework.data.util.Pair; +import org.springframework.test.util.ReflectionTestUtils; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +class SpelTagValueExpressionResolverTest { + + final SpelTagValueExpressionResolver resolver = new SpelTagValueExpressionResolver(); + + @ParameterizedTest + @MethodSource + void checkValidExpression(Object value, String expression, String expected) { + assertThat(resolver.resolve(expression, value)).isEqualTo(expected); + } + + static Stream checkValidExpression() { + return Stream.of( + Arguments.of("foo", "length", "3"), + Arguments.of("foo", "isEmpty", "false"), + Arguments.of(Pair.of("left", "right"), "first", "left"), + Arguments.of(Map.of("foo", "bar"), "['foo']", "bar"), + Arguments.of(Map.of("foo", "bar"), "['baz']", null), + Arguments.of(Map.of("foo", Pair.of(1, 2)), "['foo'].first", "1"), + Arguments.of(Map.of("foo", Pair.of(1, 2)), "['bar']?.first", null)); + } + + @ParameterizedTest + @MethodSource + void checkInvalidExpression(Object value, String expression) { + assertThatExceptionOfType(IllegalStateException.class).isThrownBy(() -> resolver.resolve(expression, value)); + } + + static Stream checkInvalidExpression() { + return Stream.of( + Arguments.of("foo", "unknownMethod"), + Arguments.of(null, "length"), + Arguments.of(Map.of("foo", Pair.of(1, 2)), "['bar'].first"), + Arguments.of(Map.of(), "invalid expression")); + } + + @Test + void checkParserReuse() throws IllegalAccessException { + var map = (Map) ReflectionTestUtils.getField(resolver,"expressionMap"); + + resolver.resolve("length", "foo"); + resolver.resolve("length", "bar"); + + assertThat(map).hasSize(1); + + resolver.resolve("isEmpty", "foo"); + assertThat(map).hasSize(2); + } +} \ No newline at end of file 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 8f6cbffee3a3..2e710199039d 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 @@ -107,6 +107,8 @@ void shouldBackOffOnCustomBeans() { assertThat(context).hasSingleBean(SpanAspect.class); assertThat(context).hasBean("customSpanTagAnnotationHandler"); assertThat(context).hasSingleBean(SpanTagAnnotationHandler.class); + assertThat(context).hasBean("customMetricsTagValueExpressionResolver"); + assertThat(context).hasSingleBean(SpelTagValueExpressionResolver.class); }); } @@ -239,6 +241,10 @@ SpanTagAnnotationHandler customSpanTagAnnotationHandler() { (aClass) -> mock(ValueExpressionResolver.class)); } + @Bean + SpelTagValueExpressionResolver customMetricsTagValueExpressionResolver() { + return mock(SpelTagValueExpressionResolver.class); + } } @Configuration(proxyBeanMethods = false) From 8ed5e0939f3aad30dbc87f4d61b2f9f14b4adfb8 Mon Sep 17 00:00:00 2001 From: Dominique Villard Date: Fri, 20 Jun 2025 18:42:49 +0200 Subject: [PATCH 3/7] Fix analysis issues Signed-off-by: Dominique Villard --- .../SpelTagValueExpressionResolverTest.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/SpelTagValueExpressionResolverTest.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/SpelTagValueExpressionResolverTest.java index 85037b36e6e9..7d0f39a9c8e2 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/SpelTagValueExpressionResolverTest.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/SpelTagValueExpressionResolverTest.java @@ -38,7 +38,7 @@ class SpelTagValueExpressionResolverTest { @ParameterizedTest @MethodSource void checkValidExpression(Object value, String expression, String expected) { - assertThat(resolver.resolve(expression, value)).isEqualTo(expected); + assertThat(this.resolver.resolve(expression, value)).isEqualTo(expected); } static Stream checkValidExpression() { @@ -55,7 +55,7 @@ static Stream checkValidExpression() { @ParameterizedTest @MethodSource void checkInvalidExpression(Object value, String expression) { - assertThatExceptionOfType(IllegalStateException.class).isThrownBy(() -> resolver.resolve(expression, value)); + assertThatExceptionOfType(IllegalStateException.class).isThrownBy(() -> this.resolver.resolve(expression, value)); } static Stream checkInvalidExpression() { @@ -67,15 +67,15 @@ static Stream checkInvalidExpression() { } @Test - void checkParserReuse() throws IllegalAccessException { - var map = (Map) ReflectionTestUtils.getField(resolver,"expressionMap"); + void checkParserReuse() { + var map = (Map) ReflectionTestUtils.getField(this.resolver,"expressionMap"); - resolver.resolve("length", "foo"); - resolver.resolve("length", "bar"); + this.resolver.resolve("length", "foo"); + this.resolver.resolve("length", "bar"); assertThat(map).hasSize(1); - resolver.resolve("isEmpty", "foo"); + this.resolver.resolve("isEmpty", "foo"); assertThat(map).hasSize(2); } } \ No newline at end of file From 1d0f59e8a8be630100a1fe5aea7e3224666894a9 Mon Sep 17 00:00:00 2001 From: Dominique Villard Date: Fri, 20 Jun 2025 18:53:15 +0200 Subject: [PATCH 4/7] Use SpelTagValueExpressionResolver to provision meter tags Signed-off-by: Dominique Villard --- .../MetricsAspectsAutoConfiguration.java | 31 ++++++++++++++++--- .../MetricsAspectsAutoConfigurationTests.java | 4 +-- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsAspectsAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsAspectsAutoConfiguration.java index e6e5696887d6..490a9a592210 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsAspectsAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsAspectsAutoConfiguration.java @@ -23,7 +23,8 @@ import io.micrometer.core.instrument.MeterRegistry; import org.aspectj.weaver.Advice; -import org.springframework.beans.factory.ObjectProvider; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.boot.actuate.autoconfigure.tracing.SpelTagValueExpressionResolver; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; @@ -47,19 +48,39 @@ public class MetricsAspectsAutoConfiguration { @Bean @ConditionalOnMissingBean - CountedAspect countedAspect(MeterRegistry registry, ObjectProvider countedMeterTagAnnotationHandler) { + CountedAspect countedAspect(MeterRegistry registry, CountedMeterTagAnnotationHandler countedMeterTagAnnotationHandler) { CountedAspect countedAspect = new CountedAspect(registry); - countedMeterTagAnnotationHandler.ifAvailable(countedAspect::setMeterTagAnnotationHandler); + countedAspect.setMeterTagAnnotationHandler(countedMeterTagAnnotationHandler); return countedAspect; } @Bean @ConditionalOnMissingBean TimedAspect timedAspect(MeterRegistry registry, - ObjectProvider meterTagAnnotationHandler) { + MeterTagAnnotationHandler meterTagAnnotationHandler) { TimedAspect timedAspect = new TimedAspect(registry); - meterTagAnnotationHandler.ifAvailable(timedAspect::setMeterTagAnnotationHandler); + timedAspect.setMeterTagAnnotationHandler(meterTagAnnotationHandler); return timedAspect; } + @Bean + @ConditionalOnMissingBean + CountedMeterTagAnnotationHandler countedMeterTagAnnotationHandler( + BeanFactory beanFactory, SpelTagValueExpressionResolver metricsTagValueExpressionResolver) { + return new CountedMeterTagAnnotationHandler(beanFactory::getBean, ignored -> metricsTagValueExpressionResolver); + } + + @Bean + @ConditionalOnMissingBean + MeterTagAnnotationHandler meterTagAnnotationHandler( + BeanFactory beanFactory, SpelTagValueExpressionResolver meterTagValueExpressionResolver) { + return new MeterTagAnnotationHandler(beanFactory::getBean, ignored -> meterTagValueExpressionResolver); + } + + @Bean + @ConditionalOnMissingBean + SpelTagValueExpressionResolver meterTagValueExpressionResolver() { + return new SpelTagValueExpressionResolver(); + } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsAspectsAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsAspectsAutoConfigurationTests.java index f2912fd395dc..91959e93083d 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsAspectsAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsAspectsAutoConfigurationTests.java @@ -66,7 +66,7 @@ void shouldConfigureAspects() { @Test void shouldConfigureMeterTagAnnotationHandler() { this.contextRunner.withUserConfiguration(MeterTagAnnotationHandlerConfiguration.class).run((context) -> { - assertThat(context).hasSingleBean(CountedAspect.class); + assertThat(context).hasSingleBean(TimedAspect.class); assertThat(ReflectionTestUtils.getField(context.getBean(TimedAspect.class), "meterTagAnnotationHandler")) .isSameAs(context.getBean(MeterTagAnnotationHandler.class)); }); @@ -76,7 +76,7 @@ void shouldConfigureMeterTagAnnotationHandler() { void shouldConfigureCounterMeterTagAnnotationHandler() { this.contextRunner.withUserConfiguration(MeterTagAnnotationHandlerConfiguration.class).run((context) -> { assertThat(context).hasSingleBean(CountedAspect.class); - assertThat(ReflectionTestUtils.getField(context.getBean(CountedAspect.class), "countedMeterTagAnnotationHandler")) + assertThat(ReflectionTestUtils.getField(context.getBean(CountedAspect.class), "meterTagAnnotationHandler")) .isSameAs(context.getBean(CountedMeterTagAnnotationHandler.class)); }); } From bf986d1f2a26f1bb4e1733e1ab5d1f92326ee0ad Mon Sep 17 00:00:00 2001 From: Dominique Villard Date: Wed, 25 Jun 2025 23:45:53 +0200 Subject: [PATCH 5/7] Move SpelTagValueExpressionResolver to metrics package. Fix checkstyle. Signed-off-by: Dominique Villard --- .../MetricsAspectsAutoConfiguration.java | 21 +++++++++-------- .../SpelTagValueExpressionResolver.java | 15 ++++++++++-- .../MicrometerTracingAutoConfiguration.java | 5 +++- .../MetricsAspectsAutoConfigurationTests.java | 3 ++- ... SpelTagValueExpressionResolverTests.java} | 23 ++++++++----------- ...crometerTracingAutoConfigurationTests.java | 2 ++ 6 files changed, 42 insertions(+), 27 deletions(-) rename spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/{tracing => metrics}/SpelTagValueExpressionResolver.java (80%) rename spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/{SpelTagValueExpressionResolverTest.java => SpelTagValueExpressionResolverTests.java} (79%) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsAspectsAutoConfiguration.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsAspectsAutoConfiguration.java index 490a9a592210..8e025b44bbd5 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsAspectsAutoConfiguration.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsAspectsAutoConfiguration.java @@ -24,7 +24,6 @@ import org.aspectj.weaver.Advice; import org.springframework.beans.factory.BeanFactory; -import org.springframework.boot.actuate.autoconfigure.tracing.SpelTagValueExpressionResolver; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; @@ -38,6 +37,7 @@ * aspects. * * @author Jonatan Ivanov + * @author Dominique Villard * @since 3.2.0 */ @AutoConfiguration(after = { MetricsAutoConfiguration.class, CompositeMeterRegistryAutoConfiguration.class }) @@ -48,7 +48,8 @@ public class MetricsAspectsAutoConfiguration { @Bean @ConditionalOnMissingBean - CountedAspect countedAspect(MeterRegistry registry, CountedMeterTagAnnotationHandler countedMeterTagAnnotationHandler) { + CountedAspect countedAspect(MeterRegistry registry, + CountedMeterTagAnnotationHandler countedMeterTagAnnotationHandler) { CountedAspect countedAspect = new CountedAspect(registry); countedAspect.setMeterTagAnnotationHandler(countedMeterTagAnnotationHandler); return countedAspect; @@ -56,8 +57,7 @@ CountedAspect countedAspect(MeterRegistry registry, CountedMeterTagAnnotationHan @Bean @ConditionalOnMissingBean - TimedAspect timedAspect(MeterRegistry registry, - MeterTagAnnotationHandler meterTagAnnotationHandler) { + TimedAspect timedAspect(MeterRegistry registry, MeterTagAnnotationHandler meterTagAnnotationHandler) { TimedAspect timedAspect = new TimedAspect(registry); timedAspect.setMeterTagAnnotationHandler(meterTagAnnotationHandler); return timedAspect; @@ -65,16 +65,17 @@ TimedAspect timedAspect(MeterRegistry registry, @Bean @ConditionalOnMissingBean - CountedMeterTagAnnotationHandler countedMeterTagAnnotationHandler( - BeanFactory beanFactory, SpelTagValueExpressionResolver metricsTagValueExpressionResolver) { - return new CountedMeterTagAnnotationHandler(beanFactory::getBean, ignored -> metricsTagValueExpressionResolver); + CountedMeterTagAnnotationHandler countedMeterTagAnnotationHandler(BeanFactory beanFactory, + SpelTagValueExpressionResolver metricsTagValueExpressionResolver) { + return new CountedMeterTagAnnotationHandler(beanFactory::getBean, + (ignored) -> metricsTagValueExpressionResolver); } @Bean @ConditionalOnMissingBean - MeterTagAnnotationHandler meterTagAnnotationHandler( - BeanFactory beanFactory, SpelTagValueExpressionResolver meterTagValueExpressionResolver) { - return new MeterTagAnnotationHandler(beanFactory::getBean, ignored -> meterTagValueExpressionResolver); + MeterTagAnnotationHandler meterTagAnnotationHandler(BeanFactory beanFactory, + SpelTagValueExpressionResolver meterTagValueExpressionResolver) { + return new MeterTagAnnotationHandler(beanFactory::getBean, (ignored) -> meterTagValueExpressionResolver); } @Bean diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/SpelTagValueExpressionResolver.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/SpelTagValueExpressionResolver.java similarity index 80% rename from spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/SpelTagValueExpressionResolver.java rename to spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/SpelTagValueExpressionResolver.java index 96fd7b7d632e..1cb0bf9bcaab 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/tracing/SpelTagValueExpressionResolver.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/SpelTagValueExpressionResolver.java @@ -14,17 +14,26 @@ * limitations under the License. */ -package org.springframework.boot.actuate.autoconfigure.tracing; +package org.springframework.boot.actuate.autoconfigure.metrics; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import io.micrometer.common.annotation.ValueExpressionResolver; +import io.micrometer.core.aop.MeterTag; +import io.micrometer.tracing.annotation.SpanTag; import org.springframework.expression.Expression; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.SimpleEvaluationContext; +/** + * Evaluates a Spel expression applied to a parameter for use in {@link MeterTag} + * {@link SpanTag} Micrometer annotations. + * + * @author Dominique Villard + * @since 3.5.1 + */ public class SpelTagValueExpressionResolver implements ValueExpressionResolver { private final Map expressionMap = new ConcurrentHashMap<>(); @@ -32,7 +41,8 @@ public class SpelTagValueExpressionResolver implements ValueExpressionResolver { @Override public String resolve(String expression, Object parameter) { try { - Expression expressionValue = this.expressionMap.computeIfAbsent(expression, SpelTagValueExpressionResolver::parseExpression); + Expression expressionValue = this.expressionMap.computeIfAbsent(expression, + SpelTagValueExpressionResolver::parseExpression); SimpleEvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build(); return expressionValue.getValue(context, parameter, String.class); } @@ -44,4 +54,5 @@ public String resolve(String expression, Object parameter) { private static Expression parseExpression(String expression) { return new SpelExpressionParser().parseExpression(expression); } + } 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 869a92ef2559..8315e214e3b8 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 @@ -30,6 +30,7 @@ import org.aspectj.weaver.Advice; import org.springframework.beans.factory.BeanFactory; +import org.springframework.boot.actuate.autoconfigure.metrics.SpelTagValueExpressionResolver; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; @@ -114,7 +115,8 @@ SpelTagValueExpressionResolver spanTagValueExpressionResolver() { @Bean @ConditionalOnMissingBean - SpanTagAnnotationHandler spanTagAnnotationHandler(BeanFactory beanFactory, SpelTagValueExpressionResolver spanTagValueExpressionResolver) { + SpanTagAnnotationHandler spanTagAnnotationHandler(BeanFactory beanFactory, + SpelTagValueExpressionResolver spanTagValueExpressionResolver) { return new SpanTagAnnotationHandler(beanFactory::getBean, (ignored) -> spanTagValueExpressionResolver); } @@ -132,4 +134,5 @@ SpanAspect spanAspect(MethodInvocationProcessor methodInvocationProcessor) { } } + } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsAspectsAutoConfigurationTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsAspectsAutoConfigurationTests.java index 91959e93083d..cfb2e79fd936 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsAspectsAutoConfigurationTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/MetricsAspectsAutoConfigurationTests.java @@ -77,7 +77,7 @@ void shouldConfigureCounterMeterTagAnnotationHandler() { this.contextRunner.withUserConfiguration(MeterTagAnnotationHandlerConfiguration.class).run((context) -> { assertThat(context).hasSingleBean(CountedAspect.class); assertThat(ReflectionTestUtils.getField(context.getBean(CountedAspect.class), "meterTagAnnotationHandler")) - .isSameAs(context.getBean(CountedMeterTagAnnotationHandler.class)); + .isSameAs(context.getBean(CountedMeterTagAnnotationHandler.class)); }); } @@ -142,6 +142,7 @@ MeterTagAnnotationHandler meterTagAnnotationHandler() { CountedMeterTagAnnotationHandler countedMeterTagAnnotationHandler() { return new CountedMeterTagAnnotationHandler(null, null); } + } } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/SpelTagValueExpressionResolverTest.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/SpelTagValueExpressionResolverTests.java similarity index 79% rename from spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/SpelTagValueExpressionResolverTest.java rename to spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/SpelTagValueExpressionResolverTests.java index 7d0f39a9c8e2..2b51df8e20d1 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/SpelTagValueExpressionResolverTest.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/SpelTagValueExpressionResolverTests.java @@ -16,6 +16,7 @@ package org.springframework.boot.actuate.autoconfigure.metrics; +import java.util.Collections; import java.util.Map; import java.util.stream.Stream; @@ -24,14 +25,13 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import org.springframework.boot.actuate.autoconfigure.tracing.SpelTagValueExpressionResolver; import org.springframework.data.util.Pair; import org.springframework.test.util.ReflectionTestUtils; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; -class SpelTagValueExpressionResolverTest { +class SpelTagValueExpressionResolverTests { final SpelTagValueExpressionResolver resolver = new SpelTagValueExpressionResolver(); @@ -42,9 +42,7 @@ void checkValidExpression(Object value, String expression, String expected) { } static Stream checkValidExpression() { - return Stream.of( - Arguments.of("foo", "length", "3"), - Arguments.of("foo", "isEmpty", "false"), + return Stream.of(Arguments.of("foo", "length", "3"), Arguments.of("foo", "isEmpty", "false"), Arguments.of(Pair.of("left", "right"), "first", "left"), Arguments.of(Map.of("foo", "bar"), "['foo']", "bar"), Arguments.of(Map.of("foo", "bar"), "['baz']", null), @@ -55,20 +53,18 @@ static Stream checkValidExpression() { @ParameterizedTest @MethodSource void checkInvalidExpression(Object value, String expression) { - assertThatExceptionOfType(IllegalStateException.class).isThrownBy(() -> this.resolver.resolve(expression, value)); + assertThatIllegalStateException().isThrownBy(() -> this.resolver.resolve(expression, value)); } static Stream checkInvalidExpression() { - return Stream.of( - Arguments.of("foo", "unknownMethod"), - Arguments.of(null, "length"), + return Stream.of(Arguments.of("foo", "unknownMethod"), Arguments.of(null, "length"), Arguments.of(Map.of("foo", Pair.of(1, 2)), "['bar'].first"), - Arguments.of(Map.of(), "invalid expression")); + Arguments.of(Collections.emptyMap(), "invalid expression")); } @Test void checkParserReuse() { - var map = (Map) ReflectionTestUtils.getField(this.resolver,"expressionMap"); + var map = (Map) ReflectionTestUtils.getField(this.resolver, "expressionMap"); this.resolver.resolve("length", "foo"); this.resolver.resolve("length", "bar"); @@ -78,4 +74,5 @@ void checkParserReuse() { this.resolver.resolve("isEmpty", "foo"); assertThat(map).hasSize(2); } -} \ No newline at end of file + +} 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 2e710199039d..a77ec566c693 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 @@ -35,6 +35,7 @@ import org.aspectj.weaver.Advice; import org.junit.jupiter.api.Test; +import org.springframework.boot.actuate.autoconfigure.metrics.SpelTagValueExpressionResolver; import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.runner.ApplicationContextRunner; @@ -245,6 +246,7 @@ SpanTagAnnotationHandler customSpanTagAnnotationHandler() { SpelTagValueExpressionResolver customMetricsTagValueExpressionResolver() { return mock(SpelTagValueExpressionResolver.class); } + } @Configuration(proxyBeanMethods = false) From 0838b5785edd36787f7b42de96335ffa9e91936e Mon Sep 17 00:00:00 2001 From: Dominique Villard Date: Wed, 25 Jun 2025 23:50:44 +0200 Subject: [PATCH 6/7] Rename variable Signed-off-by: Dominique Villard --- .../autoconfigure/metrics/SpelTagValueExpressionResolver.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/SpelTagValueExpressionResolver.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/SpelTagValueExpressionResolver.java index 1cb0bf9bcaab..559bf4f2dfe0 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/SpelTagValueExpressionResolver.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/SpelTagValueExpressionResolver.java @@ -41,10 +41,10 @@ public class SpelTagValueExpressionResolver implements ValueExpressionResolver { @Override public String resolve(String expression, Object parameter) { try { - Expression expressionValue = this.expressionMap.computeIfAbsent(expression, + Expression parsedExpression = this.expressionMap.computeIfAbsent(expression, SpelTagValueExpressionResolver::parseExpression); SimpleEvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build(); - return expressionValue.getValue(context, parameter, String.class); + return parsedExpression.getValue(context, parameter, String.class); } catch (Exception ex) { throw new IllegalStateException("Unable to evaluate SpEL expression '%s'".formatted(expression), ex); From 5ba8b8316ad17a5202b862bb773bf26838c0cdff Mon Sep 17 00:00:00 2001 From: Dominique Villard Date: Thu, 26 Jun 2025 13:07:57 +0200 Subject: [PATCH 7/7] Revert SpelTagValueExpressionResolver code Signed-off-by: Dominique Villard --- .../SpelTagValueExpressionResolver.java | 18 ++----- .../SpelTagValueExpressionResolverTests.java | 48 +++---------------- 2 files changed, 12 insertions(+), 54 deletions(-) diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/SpelTagValueExpressionResolver.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/SpelTagValueExpressionResolver.java index 559bf4f2dfe0..97090918a2f5 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/SpelTagValueExpressionResolver.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/main/java/org/springframework/boot/actuate/autoconfigure/metrics/SpelTagValueExpressionResolver.java @@ -16,14 +16,12 @@ package org.springframework.boot.actuate.autoconfigure.metrics; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - import io.micrometer.common.annotation.ValueExpressionResolver; import io.micrometer.core.aop.MeterTag; import io.micrometer.tracing.annotation.SpanTag; import org.springframework.expression.Expression; +import org.springframework.expression.ExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.support.SimpleEvaluationContext; @@ -32,27 +30,21 @@ * {@link SpanTag} Micrometer annotations. * * @author Dominique Villard - * @since 3.5.1 + * @since 4.0.0 */ public class SpelTagValueExpressionResolver implements ValueExpressionResolver { - private final Map expressionMap = new ConcurrentHashMap<>(); - @Override public String resolve(String expression, Object parameter) { try { - Expression parsedExpression = this.expressionMap.computeIfAbsent(expression, - SpelTagValueExpressionResolver::parseExpression); SimpleEvaluationContext context = SimpleEvaluationContext.forReadOnlyDataBinding().build(); - return parsedExpression.getValue(context, parameter, String.class); + ExpressionParser expressionParser = new SpelExpressionParser(); + Expression expressionToEvaluate = expressionParser.parseExpression(expression); + return expressionToEvaluate.getValue(context, parameter, String.class); } catch (Exception ex) { throw new IllegalStateException("Unable to evaluate SpEL expression '%s'".formatted(expression), ex); } } - private static Expression parseExpression(String expression) { - return new SpelExpressionParser().parseExpression(expression); - } - } diff --git a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/SpelTagValueExpressionResolverTests.java b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/SpelTagValueExpressionResolverTests.java index 2b51df8e20d1..f4b6cd02adaa 100644 --- a/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/SpelTagValueExpressionResolverTests.java +++ b/spring-boot-project/spring-boot-actuator-autoconfigure/src/test/java/org/springframework/boot/actuate/autoconfigure/metrics/SpelTagValueExpressionResolverTests.java @@ -16,17 +16,11 @@ package org.springframework.boot.actuate.autoconfigure.metrics; -import java.util.Collections; import java.util.Map; -import java.util.stream.Stream; import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; import org.springframework.data.util.Pair; -import org.springframework.test.util.ReflectionTestUtils; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; @@ -35,44 +29,16 @@ class SpelTagValueExpressionResolverTests { final SpelTagValueExpressionResolver resolver = new SpelTagValueExpressionResolver(); - @ParameterizedTest - @MethodSource - void checkValidExpression(Object value, String expression, String expected) { - assertThat(this.resolver.resolve(expression, value)).isEqualTo(expected); - } - - static Stream checkValidExpression() { - return Stream.of(Arguments.of("foo", "length", "3"), Arguments.of("foo", "isEmpty", "false"), - Arguments.of(Pair.of("left", "right"), "first", "left"), - Arguments.of(Map.of("foo", "bar"), "['foo']", "bar"), - Arguments.of(Map.of("foo", "bar"), "['baz']", null), - Arguments.of(Map.of("foo", Pair.of(1, 2)), "['foo'].first", "1"), - Arguments.of(Map.of("foo", Pair.of(1, 2)), "['bar']?.first", null)); - } - - @ParameterizedTest - @MethodSource - void checkInvalidExpression(Object value, String expression) { - assertThatIllegalStateException().isThrownBy(() -> this.resolver.resolve(expression, value)); - } - - static Stream checkInvalidExpression() { - return Stream.of(Arguments.of("foo", "unknownMethod"), Arguments.of(null, "length"), - Arguments.of(Map.of("foo", Pair.of(1, 2)), "['bar'].first"), - Arguments.of(Collections.emptyMap(), "invalid expression")); + @Test + void checkValidExpression() { + var value = Map.of("foo", Pair.of(1, 2)); + assertThat(this.resolver.resolve("['foo'].first", value)).isEqualTo("1"); } @Test - void checkParserReuse() { - var map = (Map) ReflectionTestUtils.getField(this.resolver, "expressionMap"); - - this.resolver.resolve("length", "foo"); - this.resolver.resolve("length", "bar"); - - assertThat(map).hasSize(1); - - this.resolver.resolve("isEmpty", "foo"); - assertThat(map).hasSize(2); + void checkInvalidExpression() { + var value = Map.of("foo", Pair.of(1, 2)); + assertThatIllegalStateException().isThrownBy(() -> this.resolver.resolve("['bar'].first", value)); } }