Skip to content

Commit 7d7f62f

Browse files
christophstroblmp911de
authored andcommitted
Add repository initialization metrics.
This commit adds support for collecting data repository startup metric (repository scanning, repository initialization) using core framework ApplicationStartup and StartupStep. Collected metrics can be stored with Java Flight Recorder when using a FlightRecorderApplicationStartup. Closes #2247. Original pull request: #2273.
1 parent 5844648 commit 7d7f62f

File tree

6 files changed

+151
-6
lines changed

6 files changed

+151
-6
lines changed

src/main/java/org/springframework/data/repository/config/RepositoryConfigurationDelegate.java

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525

2626
import org.apache.commons.logging.Log;
2727
import org.apache.commons.logging.LogFactory;
28-
28+
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
2929
import org.springframework.beans.factory.config.DependencyDescriptor;
3030
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
3131
import org.springframework.beans.factory.support.AbstractBeanDefinition;
@@ -34,12 +34,15 @@
3434
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
3535
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
3636
import org.springframework.context.annotation.ContextAnnotationAutowireCandidateResolver;
37+
import org.springframework.context.support.GenericApplicationContext;
3738
import org.springframework.core.env.Environment;
3839
import org.springframework.core.env.EnvironmentCapable;
3940
import org.springframework.core.env.StandardEnvironment;
4041
import org.springframework.core.io.ResourceLoader;
4142
import org.springframework.core.io.support.SpringFactoriesLoader;
4243
import org.springframework.core.log.LogMessage;
44+
import org.springframework.core.metrics.ApplicationStartup;
45+
import org.springframework.core.metrics.StartupStep;
4346
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
4447
import org.springframework.lang.Nullable;
4548
import org.springframework.util.Assert;
@@ -143,6 +146,11 @@ public List<BeanComponentDefinition> registerRepositoriesIn(BeanDefinitionRegist
143146
configurationSource.getBasePackages().stream().collect(Collectors.joining(", "))));
144147
}
145148

149+
ApplicationStartup startup = getStartup(registry);
150+
StartupStep repoScan = startup.start("spring.data.repository.scanning");
151+
repoScan.tag("data-module", extension.getModuleName());
152+
repoScan.tag("packages", configurationSource.getBasePackages().stream().collect(Collectors.joining(", ")));
153+
146154
watch.start();
147155

148156
Collection<RepositoryConfiguration<RepositoryConfigurationSource>> configurations = extension
@@ -184,6 +192,9 @@ public List<BeanComponentDefinition> registerRepositoriesIn(BeanDefinitionRegist
184192

185193
watch.stop();
186194

195+
repoScan.tag("repository.count", Integer.toString(configurations.size()));
196+
repoScan.end();
197+
187198
if (logger.isInfoEnabled()) {
188199
logger.info(LogMessage.format("Finished Spring Data repository scanning in %s ms. Found %s %s repository interfaces.", //
189200
watch.getLastTaskTimeMillis(), configurations.size(), extension.getModuleName()));
@@ -208,7 +219,6 @@ private static void potentiallyLazifyRepositories(Map<String, RepositoryConfigur
208219
}
209220

210221
DefaultListableBeanFactory beanFactory = DefaultListableBeanFactory.class.cast(registry);
211-
212222
AutowireCandidateResolver resolver = beanFactory.getAutowireCandidateResolver();
213223

214224
if (!Arrays.asList(ContextAnnotationAutowireCandidateResolver.class, LazyRepositoryInjectionPointResolver.class)
@@ -253,6 +263,23 @@ private boolean multipleStoresDetected() {
253263
return multipleModulesFound;
254264
}
255265

266+
ApplicationStartup getStartup(BeanDefinitionRegistry registry) {
267+
268+
if(ConfigurableBeanFactory.class.isInstance(registry)) {
269+
return ((ConfigurableBeanFactory)registry).getApplicationStartup();
270+
}
271+
272+
if (DefaultListableBeanFactory.class.isInstance(registry)) {
273+
return ((DefaultListableBeanFactory)registry).getBeanProvider(ApplicationStartup.class).getIfAvailable(() -> ApplicationStartup.DEFAULT);
274+
}
275+
276+
if(GenericApplicationContext.class.isInstance(registry)) {
277+
return ((GenericApplicationContext)registry).getDefaultListableBeanFactory().getApplicationStartup();
278+
}
279+
280+
return ApplicationStartup.DEFAULT;
281+
}
282+
256283
/**
257284
* Customer {@link ContextAnnotationAutowireCandidateResolver} that also considers all injection points for lazy
258285
* repositories lazy.

src/main/java/org/springframework/data/repository/core/support/RepositoryComposition.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,7 @@ public BiFunction<Method, Object[], Object[]> getArgumentConverter() {
364364
* Value object representing an ordered list of {@link RepositoryFragment fragments}.
365365
*
366366
* @author Mark Paluch
367+
* @author Christoph Strobl
367368
*/
368369
public static class RepositoryFragments implements Streamable<RepositoryFragment<?>> {
369370

@@ -550,6 +551,16 @@ private static Method findMethod(InvokedMethod invokedMethod, MethodLookup looku
550551
return null;
551552
}
552553

554+
/**
555+
* Returns the number of {@link RepositoryFragment fragments} available.
556+
*
557+
* @return the number of {@link RepositoryFragment fragments}.
558+
* @since 2.5
559+
*/
560+
public int size() {
561+
return fragments.size();
562+
}
563+
553564
/*
554565
* (non-Javadoc)
555566
* @see java.lang.Object#toString()

src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,12 @@
3535
import org.springframework.beans.factory.BeanClassLoaderAware;
3636
import org.springframework.beans.factory.BeanFactory;
3737
import org.springframework.beans.factory.BeanFactoryAware;
38+
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
3839
import org.springframework.core.convert.support.DefaultConversionService;
3940
import org.springframework.core.convert.support.GenericConversionService;
4041
import org.springframework.core.log.LogMessage;
42+
import org.springframework.core.metrics.ApplicationStartup;
43+
import org.springframework.core.metrics.StartupStep;
4144
import org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor;
4245
import org.springframework.data.projection.ProjectionFactory;
4346
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
@@ -272,15 +275,30 @@ public <T> T getRepository(Class<T> repositoryInterface, RepositoryFragments fra
272275
Assert.notNull(repositoryInterface, "Repository interface must not be null!");
273276
Assert.notNull(fragments, "RepositoryFragments must not be null!");
274277

278+
ApplicationStartup applicationStartup = getStartup();
279+
280+
StartupStep repositoryInit = applicationStartup.start("spring.data.repository.init");
281+
repositoryInit.tag("repository", () -> repositoryInterface.getName());
282+
283+
StartupStep repositoryMetadataStep = applicationStartup.start("spring.data.repository.metadata");
275284
RepositoryMetadata metadata = getRepositoryMetadata(repositoryInterface);
285+
repositoryMetadataStep.end();
286+
287+
StartupStep repositoryCompositionStep = applicationStartup.start("spring.data.repository.composition");
288+
repositoryCompositionStep.tag("fragment.count", () -> String.valueOf(fragments.size()));
289+
276290
RepositoryComposition composition = getRepositoryComposition(metadata, fragments);
277291
RepositoryInformation information = getRepositoryInformation(metadata, composition);
292+
repositoryCompositionStep.end();
278293

279294
validate(information, composition);
280295

296+
StartupStep repositoryTargetStep = applicationStartup.start("spring.data.repository.target");
281297
Object target = getTargetRepository(information);
298+
repositoryTargetStep.end();
282299

283300
// Create proxy
301+
StartupStep repositoryProxyStep = applicationStartup.start("spring.data.repository.proxy");
284302
ProxyFactory result = new ProxyFactory();
285303
result.setTarget(target);
286304
result.setInterfaces(repositoryInterface, Repository.class, TransactionalProxy.class);
@@ -291,31 +309,56 @@ public <T> T getRepository(Class<T> repositoryInterface, RepositoryFragments fra
291309

292310
result.addAdvisor(ExposeInvocationInterceptor.ADVISOR);
293311

294-
postProcessors.forEach(processor -> processor.postProcess(result, information));
312+
StartupStep repositoryPostprocessorsStep = applicationStartup.start("spring.data.repository.postprocessors");
313+
postProcessors.forEach(processor -> {
314+
315+
StartupStep singlePostProcessor = applicationStartup.start("spring.data.repository.postprocessor");
316+
singlePostProcessor.tag("type", processor.getClass().getName());
317+
processor.postProcess(result, information);
318+
singlePostProcessor.end();
319+
});
320+
repositoryPostprocessorsStep.end();
295321

296322
if (DefaultMethodInvokingMethodInterceptor.hasDefaultMethods(repositoryInterface)) {
297323
result.addAdvice(new DefaultMethodInvokingMethodInterceptor());
298324
}
299325

326+
StartupStep queryExecutorsStep = applicationStartup.start("spring.data.repository.queryexecutors");
300327
ProjectionFactory projectionFactory = getProjectionFactory(classLoader, beanFactory);
301328
Optional<QueryLookupStrategy> queryLookupStrategy = getQueryLookupStrategy(queryLookupStrategyKey,
302329
evaluationContextProvider);
303330
result.addAdvice(new QueryExecutorMethodInterceptor(information, projectionFactory, queryLookupStrategy,
304331
namedQueries, queryPostProcessors, methodInvocationListeners));
332+
queryExecutorsStep.end();
305333

306334
composition = composition.append(RepositoryFragment.implemented(target));
307335
result.addAdvice(new ImplementationMethodExecutionInterceptor(information, composition, methodInvocationListeners));
308336

309337
T repository = (T) result.getProxy(classLoader);
338+
repositoryProxyStep.end();
339+
repositoryInit.end();
310340

311341
if (logger.isDebugEnabled()) {
312-
logger
313-
.debug(LogMessage.format("Finished creation of repository instance for {}.", repositoryInterface.getName()));
342+
logger.debug(LogMessage.format("Finished creation of repository instance for {}.",
343+
repositoryInterface.getName()));
314344
}
315345

316346
return repository;
317347
}
318348

349+
ApplicationStartup getStartup() {
350+
351+
try {
352+
353+
ApplicationStartup applicationStartup = beanFactory != null ? beanFactory.getBean(ApplicationStartup.class)
354+
: ApplicationStartup.DEFAULT;
355+
356+
return applicationStartup != null ? applicationStartup : ApplicationStartup.DEFAULT;
357+
} catch (NoSuchBeanDefinitionException e) {
358+
return ApplicationStartup.DEFAULT;
359+
}
360+
}
361+
319362
/**
320363
* Returns the {@link ProjectionFactory} to be used with the repository instances created.
321364
*

src/test/java/org/springframework/data/repository/config/RepositoryConfigurationDelegateUnitTests.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import org.junit.jupiter.api.Test;
2121
import org.junit.jupiter.api.extension.ExtendWith;
2222
import org.mockito.Mock;
23+
import org.mockito.Mockito;
2324
import org.mockito.junit.jupiter.MockitoExtension;
2425
import org.mockito.junit.jupiter.MockitoSettings;
2526
import org.mockito.quality.Strictness;
@@ -35,6 +36,8 @@
3536
import org.springframework.context.annotation.FilterType;
3637
import org.springframework.context.support.GenericApplicationContext;
3738
import org.springframework.core.env.StandardEnvironment;
39+
import org.springframework.core.metrics.ApplicationStartup;
40+
import org.springframework.core.metrics.StartupStep;
3841
import org.springframework.core.type.StandardAnnotationMetadata;
3942
import org.springframework.data.repository.config.RepositoryConfigurationDelegate.LazyRepositoryInjectionPointResolver;
4043
import org.springframework.data.repository.sample.AddressRepository;
@@ -107,6 +110,26 @@ private static ListableBeanFactory assertLazyRepositoryBeanSetup(Class<?> config
107110
return context.getDefaultListableBeanFactory();
108111
}
109112

113+
@Test // DATACMNS-1832
114+
void writesRepositoryScanningMetrics() {
115+
116+
ApplicationStartup startup = Mockito.spy(ApplicationStartup.DEFAULT);
117+
118+
StandardEnvironment environment = new StandardEnvironment();
119+
GenericApplicationContext context = new GenericApplicationContext();
120+
context.setApplicationStartup(startup);
121+
122+
RepositoryConfigurationSource configSource = new AnnotationRepositoryConfigurationSource(
123+
new StandardAnnotationMetadata(TestConfig.class, true), EnableRepositories.class, context, environment,
124+
context.getDefaultListableBeanFactory());
125+
126+
RepositoryConfigurationDelegate delegate = new RepositoryConfigurationDelegate(configSource, context, environment);
127+
128+
delegate.registerRepositoriesIn(context, extension);
129+
130+
Mockito.verify(startup).start("spring.data.repository.scanning");
131+
}
132+
110133
@EnableRepositories(basePackageClasses = ProductRepository.class)
111134
static class TestConfig {}
112135

src/test/java/org/springframework/data/repository/core/support/DummyRepositoryFactory.java

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,32 +19,40 @@
1919

2020
import java.lang.reflect.Method;
2121
import java.util.Optional;
22+
import java.util.function.Supplier;
2223

24+
import org.mockito.ArgumentMatchers;
2325
import org.mockito.Mockito;
26+
import org.springframework.beans.factory.BeanFactory;
27+
import org.springframework.core.metrics.ApplicationStartup;
28+
import org.springframework.core.metrics.StartupStep;
2429
import org.springframework.data.projection.ProjectionFactory;
2530
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
2631
import org.springframework.data.repository.core.EntityInformation;
2732
import org.springframework.data.repository.core.NamedQueries;
2833
import org.springframework.data.repository.core.RepositoryInformation;
2934
import org.springframework.data.repository.core.RepositoryMetadata;
3035
import org.springframework.data.repository.core.support.RepositoryComposition.RepositoryFragments;
31-
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
3236
import org.springframework.data.repository.query.QueryLookupStrategy;
3337
import org.springframework.data.repository.query.QueryLookupStrategy.Key;
38+
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
3439
import org.springframework.data.repository.query.RepositoryQuery;
3540

3641
/**
3742
* Dummy implementation for {@link RepositoryFactorySupport} that is equipped with mocks to simulate behavior for test
3843
* cases.
3944
*
4045
* @author Oliver Gierke
46+
* @author Christoph Strobl
4147
*/
4248
public class DummyRepositoryFactory extends RepositoryFactorySupport {
4349

4450
public final MyRepositoryQuery queryOne = mock(MyRepositoryQuery.class);
4551
public final RepositoryQuery queryTwo = mock(RepositoryQuery.class);
4652
public final QueryLookupStrategy strategy = mock(QueryLookupStrategy.class);
4753

54+
private final ApplicationStartup applicationStartup;
55+
4856
@SuppressWarnings("unchecked") private final QuerydslPredicateExecutor<Object> querydsl = mock(
4957
QuerydslPredicateExecutor.class);
5058
private final Object repository;
@@ -55,6 +63,16 @@ public DummyRepositoryFactory(Object repository) {
5563

5664
when(strategy.resolveQuery(Mockito.any(Method.class), Mockito.any(RepositoryMetadata.class),
5765
Mockito.any(ProjectionFactory.class), Mockito.any(NamedQueries.class))).thenReturn(queryOne);
66+
67+
this.applicationStartup = mock(ApplicationStartup.class);
68+
StartupStep startupStep = mock(StartupStep.class);
69+
when(applicationStartup.start(anyString())).thenReturn(startupStep);
70+
when(startupStep.tag(anyString(), anyString())).thenReturn(startupStep);
71+
when(startupStep.tag(anyString(), ArgumentMatchers.<Supplier<String>> any())).thenReturn(startupStep);
72+
73+
BeanFactory beanFactory = Mockito.mock(BeanFactory.class);
74+
when(beanFactory.getBean(ApplicationStartup.class)).thenReturn(applicationStartup);
75+
setBeanFactory(beanFactory);
5876
}
5977

6078
/*
@@ -109,10 +127,15 @@ protected RepositoryFragments getRepositoryFragments(RepositoryMetadata metadata
109127
: fragments;
110128
}
111129

130+
ApplicationStartup getApplicationStartup() {
131+
return this.applicationStartup;
132+
}
133+
112134
/**
113135
* @author Mark Paluch
114136
*/
115137
public interface MyRepositoryQuery extends RepositoryQuery {
116138

117139
}
140+
118141
}

src/test/java/org/springframework/data/repository/core/support/RepositoryFactorySupportUnitTests.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,15 @@
3535
import org.junit.jupiter.api.Test;
3636
import org.junit.jupiter.api.extension.ExtendWith;
3737
import org.mockito.ArgumentCaptor;
38+
import org.mockito.InOrder;
3839
import org.mockito.Mock;
3940
import org.mockito.Mockito;
4041
import org.mockito.junit.jupiter.MockitoExtension;
4142
import org.mockito.junit.jupiter.MockitoSettings;
4243
import org.mockito.quality.Strictness;
4344
import org.springframework.aop.framework.ProxyFactory;
4445
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
46+
import org.springframework.core.metrics.ApplicationStartup;
4547
import org.springframework.dao.EmptyResultDataAccessException;
4648
import org.springframework.data.domain.Page;
4749
import org.springframework.data.domain.PageImpl;
@@ -405,6 +407,22 @@ void considersNullabilityForKotlinInterfaceProperties() {
405407
assertThatThrownBy(repository::getFindRouteQuery).isInstanceOf(EmptyResultDataAccessException.class);
406408
}
407409

410+
@Test // DATACMNS-1832
411+
void callsApplicationStartupOnRepositoryInitialization() {
412+
413+
factory.getRepository(ObjectRepository.class, backingRepo);
414+
415+
ApplicationStartup startup = factory.getApplicationStartup();
416+
417+
InOrder orderedInvocation = Mockito.inOrder(startup);
418+
orderedInvocation.verify(startup).start("spring.data.repository.init");
419+
orderedInvocation.verify(startup).start("spring.data.repository.metadata");
420+
orderedInvocation.verify(startup).start("spring.data.repository.composition");
421+
orderedInvocation.verify(startup).start("spring.data.repository.target");
422+
orderedInvocation.verify(startup).start("spring.data.repository.proxy");
423+
orderedInvocation.verify(startup).start("spring.data.repository.postprocessors");
424+
}
425+
408426
private ConvertingRepository prepareConvertingRepository(final Object expectedValue) {
409427

410428
when(factory.queryOne.execute(any(Object[].class))).then(invocation -> {

0 commit comments

Comments
 (0)