Skip to content

Commit f03f74f

Browse files
committed
Add Spring Data Repository metrics support
Add support for Spring Data Repository metrics by integrating with Spring Data's new `RepositoryMethodInvocationListener` support. Closes gh-22217
1 parent 1893f93 commit f03f74f

File tree

16 files changed

+1067
-0
lines changed

16 files changed

+1067
-0
lines changed

spring-boot-project/spring-boot-actuator-autoconfigure/build.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ dependencies {
9595
optional("org.springframework.amqp:spring-rabbit")
9696
optional("org.springframework.data:spring-data-cassandra")
9797
optional("org.springframework.data:spring-data-couchbase")
98+
optional("org.springframework.data:spring-data-jpa")
9899
optional("org.springframework.data:spring-data-ldap")
99100
optional("org.springframework.data:spring-data-mongodb")
100101
optional("org.springframework.data:spring-data-redis")

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

+43
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ public class MetricsProperties {
5454

5555
private final Web web = new Web();
5656

57+
private final Data data = new Data();
58+
5759
private final Distribution distribution = new Distribution();
5860

5961
public boolean isUseGlobalRegistry() {
@@ -76,6 +78,10 @@ public Web getWeb() {
7678
return this.web;
7779
}
7880

81+
public Data getData() {
82+
return this.data;
83+
}
84+
7985
public Distribution getDistribution() {
8086
return this.distribution;
8187
}
@@ -213,6 +219,43 @@ public void setIgnoreTrailingSlash(boolean ignoreTrailingSlash) {
213219

214220
}
215221

222+
public static class Data {
223+
224+
private final Repository repository = new Repository();
225+
226+
public Repository getRepository() {
227+
return this.repository;
228+
}
229+
230+
public static class Repository {
231+
232+
/**
233+
* Name of the metric for sent requests.
234+
*/
235+
private String metricName = "spring.data.repository.invocations";
236+
237+
/**
238+
* Auto-timed request settings.
239+
*/
240+
@NestedConfigurationProperty
241+
private final AutoTimeProperties autotime = new AutoTimeProperties();
242+
243+
public String getMetricName() {
244+
return this.metricName;
245+
}
246+
247+
public void setMetricName(String metricName) {
248+
this.metricName = metricName;
249+
}
250+
251+
public AutoTimeProperties getAutotime() {
252+
return this.autotime;
253+
}
254+
255+
}
256+
257+
}
258+
216259
public static class Distribution {
217260

218261
/**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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+
package org.springframework.boot.actuate.autoconfigure.metrics.data;
18+
19+
import org.springframework.beans.BeansException;
20+
import org.springframework.beans.factory.config.BeanPostProcessor;
21+
import org.springframework.boot.actuate.metrics.data.MetricsRepositoryMethodInvocationListener;
22+
import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
23+
import org.springframework.data.repository.core.support.RepositoryFactoryCustomizer;
24+
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
25+
26+
/**
27+
* {@link BeanPostProcessor} to apply a {@link MetricsRepositoryMethodInvocationListener}
28+
* to all {@link RepositoryFactorySupport repository factories}.
29+
*
30+
* @author Phillip Webb
31+
*/
32+
class MetricsRepositoryMethodInvocationListenerBeanPostProcessor implements BeanPostProcessor {
33+
34+
private final RepositoryFactoryCustomizer customizer;
35+
36+
MetricsRepositoryMethodInvocationListenerBeanPostProcessor(MetricsRepositoryMethodInvocationListener listener) {
37+
this.customizer = (repositoryFactory) -> repositoryFactory.addInvocationListener(listener);
38+
}
39+
40+
@Override
41+
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
42+
if (bean instanceof RepositoryFactoryBeanSupport) {
43+
((RepositoryFactoryBeanSupport<?, ?, ?>) bean).addRepositoryFactoryCustomizer(this.customizer);
44+
}
45+
return bean;
46+
}
47+
48+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
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+
package org.springframework.boot.actuate.autoconfigure.metrics.data;
18+
19+
import io.micrometer.core.instrument.MeterRegistry;
20+
21+
import org.springframework.boot.actuate.autoconfigure.metrics.CompositeMeterRegistryAutoConfiguration;
22+
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsAutoConfiguration;
23+
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsProperties;
24+
import org.springframework.boot.actuate.autoconfigure.metrics.MetricsProperties.Data.Repository;
25+
import org.springframework.boot.actuate.autoconfigure.metrics.export.simple.SimpleMetricsExportAutoConfiguration;
26+
import org.springframework.boot.actuate.metrics.data.DefaultRepositoryTagsProvider;
27+
import org.springframework.boot.actuate.metrics.data.MetricsRepositoryMethodInvocationListener;
28+
import org.springframework.boot.actuate.metrics.data.RepositoryTagsProvider;
29+
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
30+
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
31+
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
32+
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
33+
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
34+
import org.springframework.boot.context.properties.EnableConfigurationProperties;
35+
import org.springframework.context.annotation.Bean;
36+
import org.springframework.context.annotation.Configuration;
37+
38+
/**
39+
* {@link EnableAutoConfiguration Auto-configuration} for Spring Data Repository metrics.
40+
*
41+
* @author Phillip Webb
42+
* @since 2.5.0
43+
*/
44+
@Configuration(proxyBeanMethods = false)
45+
@ConditionalOnClass(org.springframework.data.repository.Repository.class)
46+
@AutoConfigureAfter({ MetricsAutoConfiguration.class, CompositeMeterRegistryAutoConfiguration.class,
47+
SimpleMetricsExportAutoConfiguration.class })
48+
@ConditionalOnBean(MeterRegistry.class)
49+
@EnableConfigurationProperties(MetricsProperties.class)
50+
public class RepositoryMetricsAutoConfiguration {
51+
52+
private final MetricsProperties properties;
53+
54+
public RepositoryMetricsAutoConfiguration(MetricsProperties properties) {
55+
this.properties = properties;
56+
}
57+
58+
@Bean
59+
@ConditionalOnMissingBean(RepositoryTagsProvider.class)
60+
public DefaultRepositoryTagsProvider repositoryTagsProvider() {
61+
return new DefaultRepositoryTagsProvider();
62+
}
63+
64+
@Bean
65+
@ConditionalOnMissingBean
66+
public MetricsRepositoryMethodInvocationListener metricsRepositoryMethodInvocationListener(MeterRegistry registry,
67+
RepositoryTagsProvider tagsProvider) {
68+
Repository properties = this.properties.getData().getRepository();
69+
return new MetricsRepositoryMethodInvocationListener(registry, tagsProvider, properties.getMetricName(),
70+
properties.getAutotime());
71+
}
72+
73+
@Bean
74+
public static MetricsRepositoryMethodInvocationListenerBeanPostProcessor metricsRepositoryMethodInvocationListenerBeanPostProcessor(
75+
MetricsRepositoryMethodInvocationListener metricsRepositoryMethodInvocationListener) {
76+
return new MetricsRepositoryMethodInvocationListenerBeanPostProcessor(
77+
metricsRepositoryMethodInvocationListener);
78+
}
79+
80+
}
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+
* Auto-configuration for Spring Data actuator metrics.
19+
*/
20+
package org.springframework.boot.actuate.autoconfigure.metrics.data;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
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+
package org.springframework.boot.actuate.autoconfigure.metrics.data;
18+
19+
import org.junit.jupiter.api.Test;
20+
import org.mockito.ArgumentCaptor;
21+
22+
import org.springframework.boot.actuate.metrics.data.MetricsRepositoryMethodInvocationListener;
23+
import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
24+
import org.springframework.data.repository.core.support.RepositoryFactoryCustomizer;
25+
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
26+
27+
import static org.assertj.core.api.Assertions.assertThat;
28+
import static org.mockito.Mockito.mock;
29+
import static org.mockito.Mockito.verify;
30+
31+
/**
32+
* Tests for {@link MetricsRepositoryMethodInvocationListenerBeanPostProcessor} .
33+
*
34+
* @author Phillip Webb
35+
*/
36+
class MetricsRepositoryMethodInvocationListenerBeanPostProcessorTests {
37+
38+
private MetricsRepositoryMethodInvocationListener listener = mock(MetricsRepositoryMethodInvocationListener.class);
39+
40+
private MetricsRepositoryMethodInvocationListenerBeanPostProcessor postProcessor = new MetricsRepositoryMethodInvocationListenerBeanPostProcessor(
41+
this.listener);
42+
43+
@Test
44+
@SuppressWarnings("rawtypes")
45+
void postProcessBeforeInitializationWhenRepositoryFactoryBeanSupportAddsListener() {
46+
RepositoryFactoryBeanSupport bean = mock(RepositoryFactoryBeanSupport.class);
47+
Object result = this.postProcessor.postProcessBeforeInitialization(bean, "name");
48+
assertThat(result).isSameAs(bean);
49+
ArgumentCaptor<RepositoryFactoryCustomizer> customizer = ArgumentCaptor
50+
.forClass(RepositoryFactoryCustomizer.class);
51+
verify(bean).addRepositoryFactoryCustomizer(customizer.capture());
52+
RepositoryFactorySupport repositoryFactory = mock(RepositoryFactorySupport.class);
53+
customizer.getValue().customize(repositoryFactory);
54+
verify(repositoryFactory).addInvocationListener(this.listener);
55+
}
56+
57+
@Test
58+
void postProcessBeforeInitializationWhenOtherBeanDoesNothing() {
59+
Object bean = new Object();
60+
Object result = this.postProcessor.postProcessBeforeInitialization(bean, "name");
61+
assertThat(result).isSameAs(bean);
62+
}
63+
64+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
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+
package org.springframework.boot.actuate.autoconfigure.metrics.data;
18+
19+
import io.micrometer.core.instrument.MeterRegistry;
20+
import org.junit.jupiter.api.Test;
21+
22+
import org.springframework.boot.actuate.autoconfigure.metrics.data.city.CityRepository;
23+
import org.springframework.boot.actuate.autoconfigure.metrics.test.MetricsRun;
24+
import org.springframework.boot.autoconfigure.AutoConfigurationPackage;
25+
import org.springframework.boot.autoconfigure.AutoConfigurations;
26+
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
27+
import org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration;
28+
import org.springframework.boot.autoconfigure.jdbc.EmbeddedDataSourceConfiguration;
29+
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
30+
import org.springframework.boot.test.context.runner.ApplicationContextRunner;
31+
import org.springframework.context.annotation.Configuration;
32+
33+
import static org.assertj.core.api.Assertions.assertThat;
34+
35+
/**
36+
* Integration tests for {@link RepositoryMetricsAutoConfiguration}.
37+
*
38+
* @author Phillip Webb
39+
*/
40+
public class RepositoryMetricsAutoConfigurationIntegrationTests {
41+
42+
private final ApplicationContextRunner contextRunner = new ApplicationContextRunner().with(MetricsRun.simple())
43+
.withConfiguration(
44+
AutoConfigurations.of(HibernateJpaAutoConfiguration.class, JpaRepositoriesAutoConfiguration.class,
45+
PropertyPlaceholderAutoConfiguration.class, RepositoryMetricsAutoConfiguration.class))
46+
.withUserConfiguration(EmbeddedDataSourceConfiguration.class, TestConfig.class);
47+
48+
@Test
49+
void repositoryMethodCallRecordsMetrics() {
50+
this.contextRunner.run((context) -> {
51+
context.getBean(CityRepository.class).count();
52+
MeterRegistry registry = context.getBean(MeterRegistry.class);
53+
assertThat(registry.get("spring.data.repository.invocations").tag("repository", "CityRepository").timer()
54+
.count()).isEqualTo(1);
55+
});
56+
}
57+
58+
@Configuration(proxyBeanMethods = false)
59+
@AutoConfigurationPackage
60+
static class TestConfig {
61+
62+
}
63+
64+
}

0 commit comments

Comments
 (0)