diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/AutoTimer.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/AutoTimer.java index 523defcbfc..4484d749e5 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/AutoTimer.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/AutoTimer.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2020 the original author or authors. + * Copyright 2012-2021 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. @@ -16,12 +16,16 @@ package org.springframework.boot.actuate.metrics; +import java.util.Set; +import java.util.function.Consumer; import java.util.function.Supplier; import io.micrometer.core.annotation.Timed; import io.micrometer.core.instrument.Timer; import io.micrometer.core.instrument.Timer.Builder; +import org.springframework.util.CollectionUtils; + /** * Strategy that can be used to apply {@link Timer Timers} automatically instead of using * {@link Timed @Timed}. @@ -94,4 +98,17 @@ public interface AutoTimer { */ void apply(Timer.Builder builder); + static void apply(AutoTimer autoTimer, String metricName, Set annotations, Consumer action) { + if (!CollectionUtils.isEmpty(annotations)) { + for (Timed annotation : annotations) { + action.accept(Timer.builder(annotation, metricName)); + } + } + else { + if (autoTimer != null && autoTimer.isEnabled()) { + action.accept(autoTimer.builder(metricName)); + } + } + } + } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/method/HandlerMethodTimedAnnotations.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/annotation/TimedAnnotations.java similarity index 72% rename from spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/method/HandlerMethodTimedAnnotations.java rename to spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/annotation/TimedAnnotations.java index 09bb1e4b01..03a8887c63 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/method/HandlerMethodTimedAnnotations.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/annotation/TimedAnnotations.java @@ -14,9 +14,10 @@ * limitations under the License. */ -package org.springframework.boot.actuate.metrics.web.method; +package org.springframework.boot.actuate.metrics.annotation; import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Method; import java.util.Collections; import java.util.Map; import java.util.Set; @@ -26,30 +27,39 @@ import io.micrometer.core.annotation.Timed; import org.springframework.core.annotation.MergedAnnotationCollectors; import org.springframework.core.annotation.MergedAnnotations; import org.springframework.util.ConcurrentReferenceHashMap; -import org.springframework.web.method.HandlerMethod; /** - * Utility used to obtain {@link Timed @Timed} annotations from a {@link HandlerMethod}. + * Utility used to obtain {@link Timed @Timed} annotations from bean methods. * * @author Phillip Webb * @since 2.5.0 */ -public final class HandlerMethodTimedAnnotations { +public final class TimedAnnotations { private static Map> cache = new ConcurrentReferenceHashMap<>(); - private HandlerMethodTimedAnnotations() { + private TimedAnnotations() { } - public static Set get(HandlerMethod handler) { - Set methodAnnotations = findTimedAnnotations(handler.getMethod()); + /** + * Return {@link Timed} annotation that should be used for the given {@code method} + * and {@code type}. + * @param method the source method + * @param type the source type + * @return the {@link Timed} annotations to use or an empty set + */ + public static Set get(Method method, Class type) { + Set methodAnnotations = findTimedAnnotations(method); if (!methodAnnotations.isEmpty()) { return methodAnnotations; } - return findTimedAnnotations(handler.getBeanType()); + return findTimedAnnotations(type); } private static Set findTimedAnnotations(AnnotatedElement element) { + if (element == null) { + return Collections.emptySet(); + } Set result = cache.get(element); if (result != null) { return result; diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/annotation/package-info.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/annotation/package-info.java new file mode 100644 index 0000000000..2903dd44bc --- /dev/null +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/annotation/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2012-2021 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. + */ + +/** + * Support classes for handler method metrics. + */ +package org.springframework.boot.actuate.metrics.annotation; diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/MetricsWebFilter.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/MetricsWebFilter.java index c34f0f4979..521532c3e9 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/MetricsWebFilter.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/MetricsWebFilter.java @@ -23,13 +23,11 @@ import java.util.concurrent.TimeUnit; import io.micrometer.core.annotation.Timed; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.Tag; -import io.micrometer.core.instrument.Timer; -import io.micrometer.core.instrument.Timer.Builder; import org.reactivestreams.Publisher; import reactor.core.publisher.Mono; import org.springframework.boot.actuate.metrics.AutoTimer; -import org.springframework.boot.actuate.metrics.web.method.HandlerMethodTimedAnnotations; +import org.springframework.boot.actuate.metrics.annotation.TimedAnnotations; import org.springframework.boot.web.reactive.error.ErrorAttributes; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; @@ -102,22 +100,19 @@ public class MetricsWebFilter implements WebFilter { private void record(ServerWebExchange exchange, Throwable cause, long start) { cause = (cause != null) ? cause : exchange.getAttribute(ErrorAttributes.ERROR_ATTRIBUTE); Object handler = exchange.getAttribute(HandlerMapping.BEST_MATCHING_HANDLER_ATTRIBUTE); - Set annotations = (handler instanceof HandlerMethod) - ? HandlerMethodTimedAnnotations.get((HandlerMethod) handler) : Collections.emptySet(); + Set annotations = getTimedAnnotations(handler); Iterable tags = this.tagsProvider.httpRequestTags(exchange, cause); long duration = System.nanoTime() - start; - if (annotations.isEmpty()) { - if (this.autoTimer.isEnabled()) { - Builder builder = this.autoTimer.builder(this.metricName); - builder.tags(tags).register(this.registry).record(duration, TimeUnit.NANOSECONDS); - } - } - else { - for (Timed annotation : annotations) { - Builder builder = Timer.builder(annotation, this.metricName); - builder.tags(tags).register(this.registry).record(duration, TimeUnit.NANOSECONDS); - } + AutoTimer.apply(this.autoTimer, this.metricName, annotations, + (builder) -> builder.tags(tags).register(this.registry).record(duration, TimeUnit.NANOSECONDS)); + } + + private Set getTimedAnnotations(Object handler) { + if (handler instanceof HandlerMethod) { + HandlerMethod handlerMethod = (HandlerMethod) handler; + return TimedAnnotations.get(handlerMethod.getMethod(), handlerMethod.getBeanType()); } + return Collections.emptySet(); } } diff --git a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/WebMvcMetricsFilter.java b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/WebMvcMetricsFilter.java index c95ace093c..f633a652af 100644 --- a/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/WebMvcMetricsFilter.java +++ b/spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/WebMvcMetricsFilter.java @@ -32,7 +32,7 @@ import io.micrometer.core.instrument.Timer.Builder; import io.micrometer.core.instrument.Timer.Sample; import org.springframework.boot.actuate.metrics.AutoTimer; -import org.springframework.boot.actuate.metrics.web.method.HandlerMethodTimedAnnotations; +import org.springframework.boot.actuate.metrics.annotation.TimedAnnotations; import org.springframework.boot.web.servlet.error.ErrorAttributes; import org.springframework.http.HttpStatus; import org.springframework.web.filter.OncePerRequestFilter; @@ -42,8 +42,8 @@ import org.springframework.web.servlet.HandlerMapping; import org.springframework.web.util.NestedServletException; /** - * Intercepts incoming HTTP requests and records metrics about Spring MVC execution time - * and results. + * Intercepts incoming HTTP requests handled by Spring MVC handlers and records metrics + * about execution time and results. * * @author Jon Schneider * @author Phillip Webb @@ -128,27 +128,24 @@ public class WebMvcMetricsFilter extends OncePerRequestFilter { private void record(TimingContext timingContext, HttpServletRequest request, HttpServletResponse response, Throwable exception) { Object handler = getHandler(request); - Set annotations = (handler instanceof HandlerMethod) - ? HandlerMethodTimedAnnotations.get((HandlerMethod) handler) : Collections.emptySet(); + Set annotations = getTimedAnnotations(handler); Timer.Sample timerSample = timingContext.getTimerSample(); - if (annotations.isEmpty()) { - if (this.autoTimer.isEnabled()) { - Builder builder = this.autoTimer.builder(this.metricName); - timerSample.stop(getTimer(builder, handler, request, response, exception)); - } - } - else { - for (Timed annotation : annotations) { - Builder builder = Timer.builder(annotation, this.metricName); - timerSample.stop(getTimer(builder, handler, request, response, exception)); - } - } + AutoTimer.apply(this.autoTimer, this.metricName, annotations, + (builder) -> timerSample.stop(getTimer(builder, handler, request, response, exception))); } private Object getHandler(HttpServletRequest request) { return request.getAttribute(HandlerMapping.BEST_MATCHING_HANDLER_ATTRIBUTE); } + private Set getTimedAnnotations(Object handler) { + if (handler instanceof HandlerMethod) { + HandlerMethod handlerMethod = (HandlerMethod) handler; + return TimedAnnotations.get(handlerMethod.getMethod(), handlerMethod.getBeanType()); + } + return Collections.emptySet(); + } + private Timer getTimer(Builder builder, Object handler, HttpServletRequest request, HttpServletResponse response, Throwable exception) { return builder.tags(this.tagsProvider.getTags(request, response, handler, exception)).register(this.registry); diff --git a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/method/HandlerMethodTimedAnnotationsTests.java b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/annotation/TimedAnnotationsTests.java similarity index 78% rename from spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/method/HandlerMethodTimedAnnotationsTests.java rename to spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/annotation/TimedAnnotationsTests.java index 9297ad231a..c563e7db8c 100644 --- a/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/web/method/HandlerMethodTimedAnnotationsTests.java +++ b/spring-boot-project/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/metrics/annotation/TimedAnnotationsTests.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package org.springframework.boot.actuate.metrics.web.method; +package org.springframework.boot.actuate.metrics.annotation; import java.lang.reflect.Method; import java.util.Set; @@ -23,22 +23,21 @@ import io.micrometer.core.annotation.Timed; import org.junit.jupiter.api.Test; import org.springframework.util.ReflectionUtils; -import org.springframework.web.method.HandlerMethod; import static org.assertj.core.api.Assertions.assertThat; /** - * Tests for {@link HandlerMethodTimedAnnotations}. + * Tests for {@link TimedAnnotations}. * * @author Phillip Webb */ -class HandlerMethodTimedAnnotationsTests { +class TimedAnnotationsTests { @Test void getWhenNoneReturnsEmptySet() { Object bean = new None(); Method method = ReflectionUtils.findMethod(bean.getClass(), "handle"); - Set annotations = HandlerMethodTimedAnnotations.get(new HandlerMethod(bean, method)); + Set annotations = TimedAnnotations.get(method, bean.getClass()); assertThat(annotations).isEmpty(); } @@ -46,7 +45,7 @@ class HandlerMethodTimedAnnotationsTests { void getWhenOnMethodReturnsMethodAnnotations() { Object bean = new OnMethod(); Method method = ReflectionUtils.findMethod(bean.getClass(), "handle"); - Set annotations = HandlerMethodTimedAnnotations.get(new HandlerMethod(bean, method)); + Set annotations = TimedAnnotations.get(method, bean.getClass()); assertThat(annotations).extracting(Timed::value).containsOnly("y", "z"); } @@ -54,7 +53,7 @@ class HandlerMethodTimedAnnotationsTests { void getWhenNonOnMethodReturnsBeanAnnotations() { Object bean = new OnBean(); Method method = ReflectionUtils.findMethod(bean.getClass(), "handle"); - Set annotations = HandlerMethodTimedAnnotations.get(new HandlerMethod(bean, method)); + Set annotations = TimedAnnotations.get(method, bean.getClass()); assertThat(annotations).extracting(Timed::value).containsOnly("y", "z"); }