Skip to content

Commit 9f16491

Browse files
committed
Rework @Timer annotation support
Rework existing `@Timer` annotation support to remove duplicate code and offer general purpose utilities that can be used in future metrics support. See gh-23112 See gh-22217
1 parent 3d7e5e3 commit 9f16491

File tree

6 files changed

+87
-49
lines changed

6 files changed

+87
-49
lines changed

spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/AutoTimer.java

+18-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2020 the original author or authors.
2+
* Copyright 2012-2021 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.
@@ -16,12 +16,16 @@
1616

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

19+
import java.util.Set;
20+
import java.util.function.Consumer;
1921
import java.util.function.Supplier;
2022

2123
import io.micrometer.core.annotation.Timed;
2224
import io.micrometer.core.instrument.Timer;
2325
import io.micrometer.core.instrument.Timer.Builder;
2426

27+
import org.springframework.util.CollectionUtils;
28+
2529
/**
2630
* Strategy that can be used to apply {@link Timer Timers} automatically instead of using
2731
* {@link Timed @Timed}.
@@ -94,4 +98,17 @@ default Timer.Builder builder(Supplier<Timer.Builder> supplier) {
9498
*/
9599
void apply(Timer.Builder builder);
96100

101+
static void apply(AutoTimer autoTimer, String metricName, Set<Timed> annotations, Consumer<Timer.Builder> action) {
102+
if (!CollectionUtils.isEmpty(annotations)) {
103+
for (Timed annotation : annotations) {
104+
action.accept(Timer.builder(annotation, metricName));
105+
}
106+
}
107+
else {
108+
if (autoTimer != null && autoTimer.isEnabled()) {
109+
action.accept(autoTimer.builder(metricName));
110+
}
111+
}
112+
}
113+
97114
}
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@
1414
* limitations under the License.
1515
*/
1616

17-
package org.springframework.boot.actuate.metrics.web.method;
17+
package org.springframework.boot.actuate.metrics.annotation;
1818

1919
import java.lang.reflect.AnnotatedElement;
20+
import java.lang.reflect.Method;
2021
import java.util.Collections;
2122
import java.util.Map;
2223
import java.util.Set;
@@ -26,30 +27,39 @@
2627
import org.springframework.core.annotation.MergedAnnotationCollectors;
2728
import org.springframework.core.annotation.MergedAnnotations;
2829
import org.springframework.util.ConcurrentReferenceHashMap;
29-
import org.springframework.web.method.HandlerMethod;
3030

3131
/**
32-
* Utility used to obtain {@link Timed @Timed} annotations from a {@link HandlerMethod}.
32+
* Utility used to obtain {@link Timed @Timed} annotations from bean methods.
3333
*
3434
* @author Phillip Webb
3535
* @since 2.5.0
3636
*/
37-
public final class HandlerMethodTimedAnnotations {
37+
public final class TimedAnnotations {
3838

3939
private static Map<AnnotatedElement, Set<Timed>> cache = new ConcurrentReferenceHashMap<>();
4040

41-
private HandlerMethodTimedAnnotations() {
41+
private TimedAnnotations() {
4242
}
4343

44-
public static Set<Timed> get(HandlerMethod handler) {
45-
Set<Timed> methodAnnotations = findTimedAnnotations(handler.getMethod());
44+
/**
45+
* Return {@link Timed} annotation that should be used for the given {@code method}
46+
* and {@code type}.
47+
* @param method the source method
48+
* @param type the source type
49+
* @return the {@link Timed} annotations to use or an empty set
50+
*/
51+
public static Set<Timed> get(Method method, Class<?> type) {
52+
Set<Timed> methodAnnotations = findTimedAnnotations(method);
4653
if (!methodAnnotations.isEmpty()) {
4754
return methodAnnotations;
4855
}
49-
return findTimedAnnotations(handler.getBeanType());
56+
return findTimedAnnotations(type);
5057
}
5158

5259
private static Set<Timed> findTimedAnnotations(AnnotatedElement element) {
60+
if (element == null) {
61+
return Collections.emptySet();
62+
}
5363
Set<Timed> result = cache.get(element);
5464
if (result != null) {
5565
return result;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
* Copyright 2012-2021 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+
/**
18+
* Support classes for handler method metrics.
19+
*/
20+
package org.springframework.boot.actuate.metrics.annotation;

spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/reactive/server/MetricsWebFilter.java

+11-16
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,11 @@
2323
import io.micrometer.core.annotation.Timed;
2424
import io.micrometer.core.instrument.MeterRegistry;
2525
import io.micrometer.core.instrument.Tag;
26-
import io.micrometer.core.instrument.Timer;
27-
import io.micrometer.core.instrument.Timer.Builder;
2826
import org.reactivestreams.Publisher;
2927
import reactor.core.publisher.Mono;
3028

3129
import org.springframework.boot.actuate.metrics.AutoTimer;
32-
import org.springframework.boot.actuate.metrics.web.method.HandlerMethodTimedAnnotations;
30+
import org.springframework.boot.actuate.metrics.annotation.TimedAnnotations;
3331
import org.springframework.boot.web.reactive.error.ErrorAttributes;
3432
import org.springframework.core.Ordered;
3533
import org.springframework.core.annotation.Order;
@@ -102,22 +100,19 @@ private void onTerminalSignal(ServerWebExchange exchange, Throwable cause, long
102100
private void record(ServerWebExchange exchange, Throwable cause, long start) {
103101
cause = (cause != null) ? cause : exchange.getAttribute(ErrorAttributes.ERROR_ATTRIBUTE);
104102
Object handler = exchange.getAttribute(HandlerMapping.BEST_MATCHING_HANDLER_ATTRIBUTE);
105-
Set<Timed> annotations = (handler instanceof HandlerMethod)
106-
? HandlerMethodTimedAnnotations.get((HandlerMethod) handler) : Collections.emptySet();
103+
Set<Timed> annotations = getTimedAnnotations(handler);
107104
Iterable<Tag> tags = this.tagsProvider.httpRequestTags(exchange, cause);
108105
long duration = System.nanoTime() - start;
109-
if (annotations.isEmpty()) {
110-
if (this.autoTimer.isEnabled()) {
111-
Builder builder = this.autoTimer.builder(this.metricName);
112-
builder.tags(tags).register(this.registry).record(duration, TimeUnit.NANOSECONDS);
113-
}
114-
}
115-
else {
116-
for (Timed annotation : annotations) {
117-
Builder builder = Timer.builder(annotation, this.metricName);
118-
builder.tags(tags).register(this.registry).record(duration, TimeUnit.NANOSECONDS);
119-
}
106+
AutoTimer.apply(this.autoTimer, this.metricName, annotations,
107+
(builder) -> builder.tags(tags).register(this.registry).record(duration, TimeUnit.NANOSECONDS));
108+
}
109+
110+
private Set<Timed> getTimedAnnotations(Object handler) {
111+
if (handler instanceof HandlerMethod) {
112+
HandlerMethod handlerMethod = (HandlerMethod) handler;
113+
return TimedAnnotations.get(handlerMethod.getMethod(), handlerMethod.getBeanType());
120114
}
115+
return Collections.emptySet();
121116
}
122117

123118
}

spring-boot-project/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/metrics/web/servlet/WebMvcMetricsFilter.java

+14-17
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
import io.micrometer.core.instrument.Timer.Sample;
3333

3434
import org.springframework.boot.actuate.metrics.AutoTimer;
35-
import org.springframework.boot.actuate.metrics.web.method.HandlerMethodTimedAnnotations;
35+
import org.springframework.boot.actuate.metrics.annotation.TimedAnnotations;
3636
import org.springframework.boot.web.servlet.error.ErrorAttributes;
3737
import org.springframework.http.HttpStatus;
3838
import org.springframework.web.filter.OncePerRequestFilter;
@@ -42,8 +42,8 @@
4242
import org.springframework.web.util.NestedServletException;
4343

4444
/**
45-
* Intercepts incoming HTTP requests and records metrics about Spring MVC execution time
46-
* and results.
45+
* Intercepts incoming HTTP requests handled by Spring MVC handlers and records metrics
46+
* about execution time and results.
4747
*
4848
* @author Jon Schneider
4949
* @author Phillip Webb
@@ -128,27 +128,24 @@ private Throwable fetchException(HttpServletRequest request) {
128128
private void record(TimingContext timingContext, HttpServletRequest request, HttpServletResponse response,
129129
Throwable exception) {
130130
Object handler = getHandler(request);
131-
Set<Timed> annotations = (handler instanceof HandlerMethod)
132-
? HandlerMethodTimedAnnotations.get((HandlerMethod) handler) : Collections.emptySet();
131+
Set<Timed> annotations = getTimedAnnotations(handler);
133132
Timer.Sample timerSample = timingContext.getTimerSample();
134-
if (annotations.isEmpty()) {
135-
if (this.autoTimer.isEnabled()) {
136-
Builder builder = this.autoTimer.builder(this.metricName);
137-
timerSample.stop(getTimer(builder, handler, request, response, exception));
138-
}
139-
}
140-
else {
141-
for (Timed annotation : annotations) {
142-
Builder builder = Timer.builder(annotation, this.metricName);
143-
timerSample.stop(getTimer(builder, handler, request, response, exception));
144-
}
145-
}
133+
AutoTimer.apply(this.autoTimer, this.metricName, annotations,
134+
(builder) -> timerSample.stop(getTimer(builder, handler, request, response, exception)));
146135
}
147136

148137
private Object getHandler(HttpServletRequest request) {
149138
return request.getAttribute(HandlerMapping.BEST_MATCHING_HANDLER_ATTRIBUTE);
150139
}
151140

141+
private Set<Timed> getTimedAnnotations(Object handler) {
142+
if (handler instanceof HandlerMethod) {
143+
HandlerMethod handlerMethod = (HandlerMethod) handler;
144+
return TimedAnnotations.get(handlerMethod.getMethod(), handlerMethod.getBeanType());
145+
}
146+
return Collections.emptySet();
147+
}
148+
152149
private Timer getTimer(Builder builder, Object handler, HttpServletRequest request, HttpServletResponse response,
153150
Throwable exception) {
154151
return builder.tags(this.tagsProvider.getTags(request, response, handler, exception)).register(this.registry);
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
* limitations under the License.
1515
*/
1616

17-
package org.springframework.boot.actuate.metrics.web.method;
17+
package org.springframework.boot.actuate.metrics.annotation;
1818

1919
import java.lang.reflect.Method;
2020
import java.util.Set;
@@ -23,38 +23,37 @@
2323
import org.junit.jupiter.api.Test;
2424

2525
import org.springframework.util.ReflectionUtils;
26-
import org.springframework.web.method.HandlerMethod;
2726

2827
import static org.assertj.core.api.Assertions.assertThat;
2928

3029
/**
31-
* Tests for {@link HandlerMethodTimedAnnotations}.
30+
* Tests for {@link TimedAnnotations}.
3231
*
3332
* @author Phillip Webb
3433
*/
35-
class HandlerMethodTimedAnnotationsTests {
34+
class TimedAnnotationsTests {
3635

3736
@Test
3837
void getWhenNoneReturnsEmptySet() {
3938
Object bean = new None();
4039
Method method = ReflectionUtils.findMethod(bean.getClass(), "handle");
41-
Set<Timed> annotations = HandlerMethodTimedAnnotations.get(new HandlerMethod(bean, method));
40+
Set<Timed> annotations = TimedAnnotations.get(method, bean.getClass());
4241
assertThat(annotations).isEmpty();
4342
}
4443

4544
@Test
4645
void getWhenOnMethodReturnsMethodAnnotations() {
4746
Object bean = new OnMethod();
4847
Method method = ReflectionUtils.findMethod(bean.getClass(), "handle");
49-
Set<Timed> annotations = HandlerMethodTimedAnnotations.get(new HandlerMethod(bean, method));
48+
Set<Timed> annotations = TimedAnnotations.get(method, bean.getClass());
5049
assertThat(annotations).extracting(Timed::value).containsOnly("y", "z");
5150
}
5251

5352
@Test
5453
void getWhenNonOnMethodReturnsBeanAnnotations() {
5554
Object bean = new OnBean();
5655
Method method = ReflectionUtils.findMethod(bean.getClass(), "handle");
57-
Set<Timed> annotations = HandlerMethodTimedAnnotations.get(new HandlerMethod(bean, method));
56+
Set<Timed> annotations = TimedAnnotations.get(method, bean.getClass());
5857
assertThat(annotations).extracting(Timed::value).containsOnly("y", "z");
5958
}
6059

0 commit comments

Comments
 (0)