From b1be1ccb37e651f596516f3c5c6e40d0eca734e4 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Tue, 17 Nov 2020 13:37:14 +0100 Subject: [PATCH 1/8] DATACMNS-1832 - Prepare issue branch. --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 9be85dfb76..4f0971f07f 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ org.springframework.data spring-data-commons - 2.5.0-SNAPSHOT + 2.5.0-DATACMNS-1832 Spring Data Core From 0044f4d03f9c87441eac97266f04695c562b4d32 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Tue, 17 Nov 2020 14:38:33 +0100 Subject: [PATCH 2/8] Add repository initialization metrics. This commit adds initial support for collecting data repository startup metrics using core framework ApplicationStartup and StartupStep. Collected metrics can be stored with Java Flight Recorder when using a FlightRecorderApplicationStartup. --- .../support/RepositoryFactorySupport.java | 28 +++++++++++++++++++ .../core/support/DummyRepositoryFactory.java | 25 ++++++++++++++++- .../RepositoryFactorySupportUnitTests.java | 7 +++++ 3 files changed, 59 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java b/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java index 4920ae0eb7..82058f92d2 100644 --- a/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java +++ b/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java @@ -22,6 +22,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.function.Consumer; import java.util.stream.Collectors; import org.aopalliance.intercept.MethodInterceptor; @@ -35,9 +36,13 @@ import org.springframework.beans.factory.BeanClassLoaderAware; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.context.ApplicationContext; import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.core.convert.support.GenericConversionService; import org.springframework.core.log.LogMessage; +import org.springframework.core.metrics.ApplicationStartup; +import org.springframework.core.metrics.StartupStep; import org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor; import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.projection.SpelAwareProxyProjectionFactory; @@ -269,12 +274,21 @@ public T getRepository(Class repositoryInterface, RepositoryFragments fra logger.debug(LogMessage.format("Initializing repository instance for %s…", repositoryInterface.getName())); } + ApplicationStartup applicationStartup = getStartup(); + Assert.notNull(repositoryInterface, "Repository interface must not be null!"); Assert.notNull(fragments, "RepositoryFragments must not be null!"); + StartupStep repositoryInit = applicationStartup.start("spring.data.repository.init"); + + StartupStep repositoryMetadataStep = applicationStartup.start("spring.data.repository.metadata"); RepositoryMetadata metadata = getRepositoryMetadata(repositoryInterface); + repositoryMetadataStep.end(); + + StartupStep repositoryCompositionStep = applicationStartup.start("spring.data.repository.metadata"); RepositoryComposition composition = getRepositoryComposition(metadata, fragments); RepositoryInformation information = getRepositoryInformation(metadata, composition); + repositoryCompositionStep.end(); validate(information, composition); @@ -291,7 +305,9 @@ public T getRepository(Class repositoryInterface, RepositoryFragments fra result.addAdvisor(ExposeInvocationInterceptor.ADVISOR); + StartupStep repositoryPostprocessorsStep = applicationStartup.start("spring.data.repository.postprocessors"); postProcessors.forEach(processor -> processor.postProcess(result, information)); + repositoryPostprocessorsStep.end(); if (DefaultMethodInvokingMethodInterceptor.hasDefaultMethods(repositoryInterface)) { result.addAdvice(new DefaultMethodInvokingMethodInterceptor()); @@ -313,9 +329,21 @@ public T getRepository(Class repositoryInterface, RepositoryFragments fra .debug(LogMessage.format("Finished creation of repository instance for {}.", repositoryInterface.getName())); } + repositoryInit.end(); return repository; } + ApplicationStartup getStartup() { + + try { + ApplicationStartup applicationStartup = beanFactory != null ? beanFactory.getBean(ApplicationStartup.class) : ApplicationStartup.DEFAULT; + return applicationStartup != null ? applicationStartup : ApplicationStartup.DEFAULT; + } + catch (NoSuchBeanDefinitionException e) { + return ApplicationStartup.DEFAULT; + } + } + /** * Returns the {@link ProjectionFactory} to be used with the repository instances created. * diff --git a/src/test/java/org/springframework/data/repository/core/support/DummyRepositoryFactory.java b/src/test/java/org/springframework/data/repository/core/support/DummyRepositoryFactory.java index e7d3ffba74..65533ae9a7 100644 --- a/src/test/java/org/springframework/data/repository/core/support/DummyRepositoryFactory.java +++ b/src/test/java/org/springframework/data/repository/core/support/DummyRepositoryFactory.java @@ -19,8 +19,13 @@ import java.lang.reflect.Method; import java.util.Optional; +import java.util.function.Supplier; +import org.mockito.ArgumentMatchers; import org.mockito.Mockito; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.core.metrics.ApplicationStartup; +import org.springframework.core.metrics.StartupStep; import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.querydsl.QuerydslPredicateExecutor; import org.springframework.data.repository.core.EntityInformation; @@ -28,9 +33,9 @@ import org.springframework.data.repository.core.RepositoryInformation; import org.springframework.data.repository.core.RepositoryMetadata; import org.springframework.data.repository.core.support.RepositoryComposition.RepositoryFragments; -import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; import org.springframework.data.repository.query.QueryLookupStrategy; import org.springframework.data.repository.query.QueryLookupStrategy.Key; +import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; import org.springframework.data.repository.query.RepositoryQuery; /** @@ -38,6 +43,7 @@ * cases. * * @author Oliver Gierke + * @author Christoph Strobl */ public class DummyRepositoryFactory extends RepositoryFactorySupport { @@ -45,6 +51,8 @@ public class DummyRepositoryFactory extends RepositoryFactorySupport { public final RepositoryQuery queryTwo = mock(RepositoryQuery.class); public final QueryLookupStrategy strategy = mock(QueryLookupStrategy.class); + private final ApplicationStartup applicationStartup; + @SuppressWarnings("unchecked") private final QuerydslPredicateExecutor querydsl = mock( QuerydslPredicateExecutor.class); private final Object repository; @@ -55,6 +63,16 @@ public DummyRepositoryFactory(Object repository) { when(strategy.resolveQuery(Mockito.any(Method.class), Mockito.any(RepositoryMetadata.class), Mockito.any(ProjectionFactory.class), Mockito.any(NamedQueries.class))).thenReturn(queryOne); + + this.applicationStartup = mock(ApplicationStartup.class); + StartupStep startupStep = mock(StartupStep.class); + when(applicationStartup.start(anyString())).thenReturn(startupStep); + when(startupStep.tag(anyString(), anyString())).thenReturn(startupStep); + when(startupStep.tag(anyString(), ArgumentMatchers.> any())).thenReturn(startupStep); + + BeanFactory beanFactory = Mockito.mock(BeanFactory.class); + when(beanFactory.getBean(ApplicationStartup.class)).thenReturn(applicationStartup); + setBeanFactory(beanFactory); } /* @@ -109,10 +127,15 @@ protected RepositoryFragments getRepositoryFragments(RepositoryMetadata metadata : fragments; } + ApplicationStartup getApplicationStartup() { + return this.applicationStartup; + } + /** * @author Mark Paluch */ public interface MyRepositoryQuery extends RepositoryQuery { } + } diff --git a/src/test/java/org/springframework/data/repository/core/support/RepositoryFactorySupportUnitTests.java b/src/test/java/org/springframework/data/repository/core/support/RepositoryFactorySupportUnitTests.java index 353338d32e..0aa59182ec 100755 --- a/src/test/java/org/springframework/data/repository/core/support/RepositoryFactorySupportUnitTests.java +++ b/src/test/java/org/springframework/data/repository/core/support/RepositoryFactorySupportUnitTests.java @@ -405,6 +405,13 @@ void considersNullabilityForKotlinInterfaceProperties() { assertThatThrownBy(repository::getFindRouteQuery).isInstanceOf(EmptyResultDataAccessException.class); } + @Test // DATACMNS-1832 + void callsApplicationStartupOnRepositoryIntialization() { + + factory.getRepository(ObjectRepository.class, backingRepo); + verify(factory.getApplicationStartup()).start("spring.data.repository.init"); + } + private ConvertingRepository prepareConvertingRepository(final Object expectedValue) { when(factory.queryOne.execute(any(Object[].class))).then(invocation -> { From 005f081d5ade06bdcdc9ed5c81e6d6799bf9ccc1 Mon Sep 17 00:00:00 2001 From: John Blum Date: Fri, 11 Dec 2020 15:25:49 -0800 Subject: [PATCH 3/8] Add more StartupSteps to ApplicationStartup (JFR). Adds a StartupStep for the creation of the target SD module Repository object (e.g. SD JPA). This could vary significantly from 1 SD module to another. Adds a StartupStep for the creation of the SD Repository Proxy. Renames the StartupStep wrapping the Repository composition logic to 'spring.data.repository.composition'. Moves the construction/initialization of the ApplicationStartup instance after the assertions in the getRepository(..) method. --- .../support/RepositoryFactorySupport.java | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java b/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java index 82058f92d2..68336b6bb0 100644 --- a/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java +++ b/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java @@ -274,27 +274,30 @@ public T getRepository(Class repositoryInterface, RepositoryFragments fra logger.debug(LogMessage.format("Initializing repository instance for %s…", repositoryInterface.getName())); } - ApplicationStartup applicationStartup = getStartup(); - Assert.notNull(repositoryInterface, "Repository interface must not be null!"); Assert.notNull(fragments, "RepositoryFragments must not be null!"); + ApplicationStartup applicationStartup = getStartup(); + StartupStep repositoryInit = applicationStartup.start("spring.data.repository.init"); StartupStep repositoryMetadataStep = applicationStartup.start("spring.data.repository.metadata"); RepositoryMetadata metadata = getRepositoryMetadata(repositoryInterface); repositoryMetadataStep.end(); - StartupStep repositoryCompositionStep = applicationStartup.start("spring.data.repository.metadata"); + StartupStep repositoryCompositionStep = applicationStartup.start("spring.data.repository.composition"); RepositoryComposition composition = getRepositoryComposition(metadata, fragments); RepositoryInformation information = getRepositoryInformation(metadata, composition); repositoryCompositionStep.end(); validate(information, composition); + StartupStep repositoryTargetStep = applicationStartup.start("spring.data.repository.target"); Object target = getTargetRepository(information); + repositoryTargetStep.end(); // Create proxy + StartupStep repositoryProxyStep = applicationStartup.start("spring.data.repository.proxy"); ProxyFactory result = new ProxyFactory(); result.setTarget(target); result.setInterfaces(repositoryInterface, Repository.class, TransactionalProxy.class); @@ -323,10 +326,11 @@ public T getRepository(Class repositoryInterface, RepositoryFragments fra result.addAdvice(new ImplementationMethodExecutionInterceptor(information, composition, methodInvocationListeners)); T repository = (T) result.getProxy(classLoader); + repositoryProxyStep.end(); if (logger.isDebugEnabled()) { - logger - .debug(LogMessage.format("Finished creation of repository instance for {}.", repositoryInterface.getName())); + logger.debug(LogMessage.format("Finished creation of repository instance for {}.", + repositoryInterface.getName())); } repositoryInit.end(); @@ -336,7 +340,11 @@ public T getRepository(Class repositoryInterface, RepositoryFragments fra ApplicationStartup getStartup() { try { - ApplicationStartup applicationStartup = beanFactory != null ? beanFactory.getBean(ApplicationStartup.class) : ApplicationStartup.DEFAULT; + + ApplicationStartup applicationStartup = beanFactory != null + ? beanFactory.getBean(ApplicationStartup.class) + : ApplicationStartup.DEFAULT; + return applicationStartup != null ? applicationStartup : ApplicationStartup.DEFAULT; } catch (NoSuchBeanDefinitionException e) { From 27587c65b4ffab3b90c6992be35659d2a435f192 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Thu, 14 Jan 2021 12:05:15 +0100 Subject: [PATCH 4/8] Additional Tags for repository initialisation metrics. Add the interface type name the repository is created for, query executors, as well as the number of fragments and measurements about potential post processors. --- .../core/support/RepositoryComposition.java | 11 ++++++++ .../support/RepositoryFactorySupport.java | 25 ++++++++++++------- .../RepositoryFactorySupportUnitTests.java | 15 +++++++++-- 3 files changed, 40 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/springframework/data/repository/core/support/RepositoryComposition.java b/src/main/java/org/springframework/data/repository/core/support/RepositoryComposition.java index 1e5027c7ae..388791a70c 100644 --- a/src/main/java/org/springframework/data/repository/core/support/RepositoryComposition.java +++ b/src/main/java/org/springframework/data/repository/core/support/RepositoryComposition.java @@ -364,6 +364,7 @@ public BiFunction getArgumentConverter() { * Value object representing an ordered list of {@link RepositoryFragment fragments}. * * @author Mark Paluch + * @author Christoph Strobl */ public static class RepositoryFragments implements Streamable> { @@ -550,6 +551,16 @@ private static Method findMethod(InvokedMethod invokedMethod, MethodLookup looku return null; } + /** + * Returns the number of {@link RepositoryFragment fragments} available. + * + * @return the number of {@link RepositoryFragment fragments}. + * @since 2.5 + */ + public int size() { + return fragments.size(); + } + /* * (non-Javadoc) * @see java.lang.Object#toString() diff --git a/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java b/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java index 68336b6bb0..4f68cdd649 100644 --- a/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java +++ b/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java @@ -22,7 +22,6 @@ import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.function.Consumer; import java.util.stream.Collectors; import org.aopalliance.intercept.MethodInterceptor; @@ -37,7 +36,6 @@ import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.BeanFactoryAware; import org.springframework.beans.factory.NoSuchBeanDefinitionException; -import org.springframework.context.ApplicationContext; import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.core.convert.support.GenericConversionService; import org.springframework.core.log.LogMessage; @@ -280,12 +278,15 @@ public T getRepository(Class repositoryInterface, RepositoryFragments fra ApplicationStartup applicationStartup = getStartup(); StartupStep repositoryInit = applicationStartup.start("spring.data.repository.init"); + repositoryInit.tag("repository", () -> repositoryInterface.getName()); StartupStep repositoryMetadataStep = applicationStartup.start("spring.data.repository.metadata"); RepositoryMetadata metadata = getRepositoryMetadata(repositoryInterface); repositoryMetadataStep.end(); StartupStep repositoryCompositionStep = applicationStartup.start("spring.data.repository.composition"); + repositoryCompositionStep.tag("fragment.count", () -> String.valueOf(fragments.size())); + RepositoryComposition composition = getRepositoryComposition(metadata, fragments); RepositoryInformation information = getRepositoryInformation(metadata, composition); repositoryCompositionStep.end(); @@ -309,31 +310,39 @@ public T getRepository(Class repositoryInterface, RepositoryFragments fra result.addAdvisor(ExposeInvocationInterceptor.ADVISOR); StartupStep repositoryPostprocessorsStep = applicationStartup.start("spring.data.repository.postprocessors"); - postProcessors.forEach(processor -> processor.postProcess(result, information)); + postProcessors.forEach(processor -> { + + StartupStep singlePostProcessor = applicationStartup.start("spring.data.repository.postprocessor"); + singlePostProcessor.tag("type", processor.getClass().getName()); + processor.postProcess(result, information); + singlePostProcessor.end(); + }); repositoryPostprocessorsStep.end(); if (DefaultMethodInvokingMethodInterceptor.hasDefaultMethods(repositoryInterface)) { result.addAdvice(new DefaultMethodInvokingMethodInterceptor()); } + StartupStep queryExecutorsStep = applicationStartup.start("spring.data.repository.queryexecutors"); ProjectionFactory projectionFactory = getProjectionFactory(classLoader, beanFactory); Optional queryLookupStrategy = getQueryLookupStrategy(queryLookupStrategyKey, evaluationContextProvider); result.addAdvice(new QueryExecutorMethodInterceptor(information, projectionFactory, queryLookupStrategy, namedQueries, queryPostProcessors, methodInvocationListeners)); + queryExecutorsStep.end(); composition = composition.append(RepositoryFragment.implemented(target)); result.addAdvice(new ImplementationMethodExecutionInterceptor(information, composition, methodInvocationListeners)); T repository = (T) result.getProxy(classLoader); repositoryProxyStep.end(); + repositoryInit.end(); if (logger.isDebugEnabled()) { logger.debug(LogMessage.format("Finished creation of repository instance for {}.", repositoryInterface.getName())); } - repositoryInit.end(); return repository; } @@ -341,13 +350,11 @@ ApplicationStartup getStartup() { try { - ApplicationStartup applicationStartup = beanFactory != null - ? beanFactory.getBean(ApplicationStartup.class) - : ApplicationStartup.DEFAULT; + ApplicationStartup applicationStartup = beanFactory != null ? beanFactory.getBean(ApplicationStartup.class) + : ApplicationStartup.DEFAULT; return applicationStartup != null ? applicationStartup : ApplicationStartup.DEFAULT; - } - catch (NoSuchBeanDefinitionException e) { + } catch (NoSuchBeanDefinitionException e) { return ApplicationStartup.DEFAULT; } } diff --git a/src/test/java/org/springframework/data/repository/core/support/RepositoryFactorySupportUnitTests.java b/src/test/java/org/springframework/data/repository/core/support/RepositoryFactorySupportUnitTests.java index 0aa59182ec..d41eb974c1 100755 --- a/src/test/java/org/springframework/data/repository/core/support/RepositoryFactorySupportUnitTests.java +++ b/src/test/java/org/springframework/data/repository/core/support/RepositoryFactorySupportUnitTests.java @@ -35,6 +35,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; +import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; @@ -42,6 +43,7 @@ import org.mockito.quality.Strictness; import org.springframework.aop.framework.ProxyFactory; import org.springframework.beans.factory.support.DefaultListableBeanFactory; +import org.springframework.core.metrics.ApplicationStartup; import org.springframework.dao.EmptyResultDataAccessException; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; @@ -406,10 +408,19 @@ void considersNullabilityForKotlinInterfaceProperties() { } @Test // DATACMNS-1832 - void callsApplicationStartupOnRepositoryIntialization() { + void callsApplicationStartupOnRepositoryInitialization() { factory.getRepository(ObjectRepository.class, backingRepo); - verify(factory.getApplicationStartup()).start("spring.data.repository.init"); + + ApplicationStartup startup = factory.getApplicationStartup(); + + InOrder orderedInvocation = Mockito.inOrder(startup); + orderedInvocation.verify(startup).start("spring.data.repository.init"); + orderedInvocation.verify(startup).start("spring.data.repository.metadata"); + orderedInvocation.verify(startup).start("spring.data.repository.composition"); + orderedInvocation.verify(startup).start("spring.data.repository.target"); + orderedInvocation.verify(startup).start("spring.data.repository.proxy"); + orderedInvocation.verify(startup).start("spring.data.repository.postprocessors"); } private ConvertingRepository prepareConvertingRepository(final Object expectedValue) { From 31eeb742103023476f00534c8c6252e5ac7e50c4 Mon Sep 17 00:00:00 2001 From: Christoph Strobl Date: Mon, 18 Jan 2021 14:40:35 +0100 Subject: [PATCH 5/8] Startup metrics for per module repository scanning. This commit adds application startup metrics for the time taken to scan the context for Spring Data repositories on a per module (JPA, MongoDB,...) base. We capture the module name, the scan base package and the number of repositories found. { "startupStep": { "name": "spring.data.repository.scanning", "id": ..., "parentId": ..., "tags": [ { "key": "data-module", "value": "MongoDB" }, { "key": "packages", "value": "com.example.demo" }, { "key": "repository.count", "value": "1" } ] } } --- .../RepositoryConfigurationDelegate.java | 31 +++++++++++++++++-- ...ositoryConfigurationDelegateUnitTests.java | 23 ++++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationDelegate.java b/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationDelegate.java index 11147d11a1..b290deea22 100644 --- a/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationDelegate.java +++ b/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationDelegate.java @@ -25,7 +25,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; - +import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.beans.factory.config.DependencyDescriptor; import org.springframework.beans.factory.parsing.BeanComponentDefinition; import org.springframework.beans.factory.support.AbstractBeanDefinition; @@ -34,12 +34,15 @@ import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.DefaultListableBeanFactory; import org.springframework.context.annotation.ContextAnnotationAutowireCandidateResolver; +import org.springframework.context.support.GenericApplicationContext; import org.springframework.core.env.Environment; import org.springframework.core.env.EnvironmentCapable; import org.springframework.core.env.StandardEnvironment; import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.support.SpringFactoriesLoader; import org.springframework.core.log.LogMessage; +import org.springframework.core.metrics.ApplicationStartup; +import org.springframework.core.metrics.StartupStep; import org.springframework.data.repository.core.support.RepositoryFactorySupport; import org.springframework.lang.Nullable; import org.springframework.util.Assert; @@ -143,6 +146,11 @@ public List registerRepositoriesIn(BeanDefinitionRegist configurationSource.getBasePackages().stream().collect(Collectors.joining(", ")))); } + ApplicationStartup startup = getStartup(registry); + StartupStep repoScan = startup.start("spring.data.repository.scanning"); + repoScan.tag("data-module", extension.getModuleName()); + repoScan.tag("packages", configurationSource.getBasePackages().stream().collect(Collectors.joining(", "))); + watch.start(); Collection> configurations = extension @@ -184,6 +192,9 @@ public List registerRepositoriesIn(BeanDefinitionRegist watch.stop(); + repoScan.tag("repository.count", Integer.toString(configurations.size())); + repoScan.end(); + if (logger.isInfoEnabled()) { logger.info(LogMessage.format("Finished Spring Data repository scanning in %s ms. Found %s %s repository interfaces.", // watch.getLastTaskTimeMillis(), configurations.size(), extension.getModuleName())); @@ -208,7 +219,6 @@ private static void potentiallyLazifyRepositories(Map ApplicationStartup.DEFAULT); + } + + if(GenericApplicationContext.class.isInstance(registry)) { + return ((GenericApplicationContext)registry).getDefaultListableBeanFactory().getApplicationStartup(); + } + + return ApplicationStartup.DEFAULT; + } + /** * Customer {@link ContextAnnotationAutowireCandidateResolver} that also considers all injection points for lazy * repositories lazy. diff --git a/src/test/java/org/springframework/data/repository/config/RepositoryConfigurationDelegateUnitTests.java b/src/test/java/org/springframework/data/repository/config/RepositoryConfigurationDelegateUnitTests.java index b221fc430b..47881af851 100644 --- a/src/test/java/org/springframework/data/repository/config/RepositoryConfigurationDelegateUnitTests.java +++ b/src/test/java/org/springframework/data/repository/config/RepositoryConfigurationDelegateUnitTests.java @@ -20,6 +20,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; @@ -35,6 +36,8 @@ import org.springframework.context.annotation.FilterType; import org.springframework.context.support.GenericApplicationContext; import org.springframework.core.env.StandardEnvironment; +import org.springframework.core.metrics.ApplicationStartup; +import org.springframework.core.metrics.StartupStep; import org.springframework.core.type.StandardAnnotationMetadata; import org.springframework.data.repository.config.RepositoryConfigurationDelegate.LazyRepositoryInjectionPointResolver; import org.springframework.data.repository.sample.AddressRepository; @@ -107,6 +110,26 @@ private static ListableBeanFactory assertLazyRepositoryBeanSetup(Class config return context.getDefaultListableBeanFactory(); } + @Test // DATACMNS-1832 + void writesRepositoryScanningMetrics() { + + ApplicationStartup startup = Mockito.spy(ApplicationStartup.DEFAULT); + + StandardEnvironment environment = new StandardEnvironment(); + GenericApplicationContext context = new GenericApplicationContext(); + context.setApplicationStartup(startup); + + RepositoryConfigurationSource configSource = new AnnotationRepositoryConfigurationSource( + new StandardAnnotationMetadata(TestConfig.class, true), EnableRepositories.class, context, environment, + context.getDefaultListableBeanFactory()); + + RepositoryConfigurationDelegate delegate = new RepositoryConfigurationDelegate(configSource, context, environment); + + delegate.registerRepositoriesIn(context, extension); + + Mockito.verify(startup).start("spring.data.repository.scanning"); + } + @EnableRepositories(basePackageClasses = ProductRepository.class) static class TestConfig {} From 579ca77bc8b3a9cc996ebfb4580449c05ff4cd04 Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Thu, 28 Jan 2021 14:34:08 +0100 Subject: [PATCH 6/8] Attach repository interface name to each repository init event. Make spring.data.repository.postprocessors conditional to reduce the number of events. Append full fragment diagnostics. --- .../support/RepositoryFactorySupport.java | 73 ++++++++++++++----- .../RepositoryFactorySupportUnitTests.java | 1 - 2 files changed, 53 insertions(+), 21 deletions(-) diff --git a/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java b/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java index 4f68cdd649..2ec1059e40 100644 --- a/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java +++ b/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java @@ -277,28 +277,52 @@ public T getRepository(Class repositoryInterface, RepositoryFragments fra ApplicationStartup applicationStartup = getStartup(); - StartupStep repositoryInit = applicationStartup.start("spring.data.repository.init"); - repositoryInit.tag("repository", () -> repositoryInterface.getName()); + StartupStep repositoryInit = onEvent(applicationStartup, "spring.data.repository.init", repositoryInterface); - StartupStep repositoryMetadataStep = applicationStartup.start("spring.data.repository.metadata"); + repositoryBaseClass.ifPresent(it -> repositoryInit.tag("baseClass", it.getName())); + + StartupStep repositoryMetadataStep = onEvent(applicationStartup, "spring.data.repository.metadata", + repositoryInterface); RepositoryMetadata metadata = getRepositoryMetadata(repositoryInterface); repositoryMetadataStep.end(); - StartupStep repositoryCompositionStep = applicationStartup.start("spring.data.repository.composition"); - repositoryCompositionStep.tag("fragment.count", () -> String.valueOf(fragments.size())); + StartupStep repositoryCompositionStep = onEvent(applicationStartup, "spring.data.repository.composition", + repositoryInterface); + repositoryCompositionStep.tag("fragment.count", String.valueOf(fragments.size())); RepositoryComposition composition = getRepositoryComposition(metadata, fragments); RepositoryInformation information = getRepositoryInformation(metadata, composition); + + repositoryCompositionStep.tag("fragments", () -> { + + StringBuilder fragmentsTag = new StringBuilder(); + + for (RepositoryFragment fragment : composition.getFragments()) { + + if (fragmentsTag.length() > 0) { + fragmentsTag.append(";"); + } + + fragmentsTag.append(fragment.getSignatureContributor().getName()); + fragmentsTag.append(fragment.getImplementation().map(it -> ":" + it.getClass().getName()).orElse("")); + } + + return fragmentsTag.toString(); + }); + repositoryCompositionStep.end(); validate(information, composition); - StartupStep repositoryTargetStep = applicationStartup.start("spring.data.repository.target"); + StartupStep repositoryTargetStep = onEvent(applicationStartup, "spring.data.repository.target", + repositoryInterface); Object target = getTargetRepository(information); + + repositoryTargetStep.tag("target", target.getClass().getName()); repositoryTargetStep.end(); // Create proxy - StartupStep repositoryProxyStep = applicationStartup.start("spring.data.repository.proxy"); + StartupStep repositoryProxyStep = onEvent(applicationStartup, "spring.data.repository.proxy", repositoryInterface); ProxyFactory result = new ProxyFactory(); result.setTarget(target); result.setInterfaces(repositoryInterface, Repository.class, TransactionalProxy.class); @@ -309,30 +333,33 @@ public T getRepository(Class repositoryInterface, RepositoryFragments fra result.addAdvisor(ExposeInvocationInterceptor.ADVISOR); - StartupStep repositoryPostprocessorsStep = applicationStartup.start("spring.data.repository.postprocessors"); - postProcessors.forEach(processor -> { - - StartupStep singlePostProcessor = applicationStartup.start("spring.data.repository.postprocessor"); - singlePostProcessor.tag("type", processor.getClass().getName()); - processor.postProcess(result, information); - singlePostProcessor.end(); - }); - repositoryPostprocessorsStep.end(); + if (!postProcessors.isEmpty()) { + StartupStep repositoryPostprocessorsStep = onEvent(applicationStartup, "spring.data.repository.postprocessors", + repositoryInterface); + postProcessors.forEach(processor -> { + + StartupStep singlePostProcessor = onEvent(applicationStartup, "spring.data.repository.postprocessor", + repositoryInterface); + singlePostProcessor.tag("type", processor.getClass().getName()); + processor.postProcess(result, information); + singlePostProcessor.end(); + }); + repositoryPostprocessorsStep.end(); + } if (DefaultMethodInvokingMethodInterceptor.hasDefaultMethods(repositoryInterface)) { result.addAdvice(new DefaultMethodInvokingMethodInterceptor()); } - StartupStep queryExecutorsStep = applicationStartup.start("spring.data.repository.queryexecutors"); ProjectionFactory projectionFactory = getProjectionFactory(classLoader, beanFactory); Optional queryLookupStrategy = getQueryLookupStrategy(queryLookupStrategyKey, evaluationContextProvider); result.addAdvice(new QueryExecutorMethodInterceptor(information, projectionFactory, queryLookupStrategy, namedQueries, queryPostProcessors, methodInvocationListeners)); - queryExecutorsStep.end(); - composition = composition.append(RepositoryFragment.implemented(target)); - result.addAdvice(new ImplementationMethodExecutionInterceptor(information, composition, methodInvocationListeners)); + RepositoryComposition compositionToUse = composition.append(RepositoryFragment.implemented(target)); + result.addAdvice( + new ImplementationMethodExecutionInterceptor(information, compositionToUse, methodInvocationListeners)); T repository = (T) result.getProxy(classLoader); repositoryProxyStep.end(); @@ -359,6 +386,12 @@ ApplicationStartup getStartup() { } } + private StartupStep onEvent(ApplicationStartup applicationStartup, String name, Class repositoryInterface) { + + StartupStep step = applicationStartup.start(name); + return step.tag("repository", repositoryInterface.getName()); + } + /** * Returns the {@link ProjectionFactory} to be used with the repository instances created. * diff --git a/src/test/java/org/springframework/data/repository/core/support/RepositoryFactorySupportUnitTests.java b/src/test/java/org/springframework/data/repository/core/support/RepositoryFactorySupportUnitTests.java index d41eb974c1..3eeb54316d 100755 --- a/src/test/java/org/springframework/data/repository/core/support/RepositoryFactorySupportUnitTests.java +++ b/src/test/java/org/springframework/data/repository/core/support/RepositoryFactorySupportUnitTests.java @@ -420,7 +420,6 @@ void callsApplicationStartupOnRepositoryInitialization() { orderedInvocation.verify(startup).start("spring.data.repository.composition"); orderedInvocation.verify(startup).start("spring.data.repository.target"); orderedInvocation.verify(startup).start("spring.data.repository.proxy"); - orderedInvocation.verify(startup).start("spring.data.repository.postprocessors"); } private ConvertingRepository prepareConvertingRepository(final Object expectedValue) { From 5d83bacce65569bd76f10529002217083c8a8aac Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Thu, 28 Jan 2021 14:51:41 +0100 Subject: [PATCH 7/8] Polishing. Reorder methods --- .../support/RepositoryFactorySupport.java | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java b/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java index 2ec1059e40..96768c5796 100644 --- a/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java +++ b/src/main/java/org/springframework/data/repository/core/support/RepositoryFactorySupport.java @@ -373,25 +373,6 @@ public T getRepository(Class repositoryInterface, RepositoryFragments fra return repository; } - ApplicationStartup getStartup() { - - try { - - ApplicationStartup applicationStartup = beanFactory != null ? beanFactory.getBean(ApplicationStartup.class) - : ApplicationStartup.DEFAULT; - - return applicationStartup != null ? applicationStartup : ApplicationStartup.DEFAULT; - } catch (NoSuchBeanDefinitionException e) { - return ApplicationStartup.DEFAULT; - } - } - - private StartupStep onEvent(ApplicationStartup applicationStartup, String name, Class repositoryInterface) { - - StartupStep step = applicationStartup.start(name); - return step.tag("repository", repositoryInterface.getName()); - } - /** * Returns the {@link ProjectionFactory} to be used with the repository instances created. * @@ -573,6 +554,25 @@ protected final R getTargetRepositoryViaReflection(Class baseClass, Objec baseClass, Arrays.stream(constructorArguments).map(Object::getClass).collect(Collectors.toList())))); } + private ApplicationStartup getStartup() { + + try { + + ApplicationStartup applicationStartup = beanFactory != null ? beanFactory.getBean(ApplicationStartup.class) + : ApplicationStartup.DEFAULT; + + return applicationStartup != null ? applicationStartup : ApplicationStartup.DEFAULT; + } catch (NoSuchBeanDefinitionException e) { + return ApplicationStartup.DEFAULT; + } + } + + private StartupStep onEvent(ApplicationStartup applicationStartup, String name, Class repositoryInterface) { + + StartupStep step = applicationStartup.start(name); + return step.tag("repository", repositoryInterface.getName()); + } + /** * Method interceptor that calls methods on the {@link RepositoryComposition}. * From 6f257c794aae58dd9e0fae6871bb3974bb0d367f Mon Sep 17 00:00:00 2001 From: Mark Paluch Date: Thu, 28 Jan 2021 14:55:51 +0100 Subject: [PATCH 8/8] Polishing. Reorder methods, add author tag, reformat code. --- .../RepositoryConfigurationDelegate.java | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationDelegate.java b/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationDelegate.java index b290deea22..28f1501e44 100644 --- a/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationDelegate.java +++ b/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationDelegate.java @@ -57,6 +57,7 @@ * @author Oliver Gierke * @author Jens Schauder * @author Mark Paluch + * @author Christoph Strobl */ public class RepositoryConfigurationDelegate { @@ -148,8 +149,9 @@ public List registerRepositoriesIn(BeanDefinitionRegist ApplicationStartup startup = getStartup(registry); StartupStep repoScan = startup.start("spring.data.repository.scanning"); - repoScan.tag("data-module", extension.getModuleName()); - repoScan.tag("packages", configurationSource.getBasePackages().stream().collect(Collectors.joining(", "))); + repoScan.tag("dataModule", extension.getModuleName()); + repoScan.tag("basePackages", + () -> configurationSource.getBasePackages().stream().collect(Collectors.joining(", "))); watch.start(); @@ -263,18 +265,14 @@ private boolean multipleStoresDetected() { return multipleModulesFound; } - ApplicationStartup getStartup(BeanDefinitionRegistry registry) { + private static ApplicationStartup getStartup(BeanDefinitionRegistry registry) { - if(ConfigurableBeanFactory.class.isInstance(registry)) { - return ((ConfigurableBeanFactory)registry).getApplicationStartup(); + if (registry instanceof ConfigurableBeanFactory) { + return ((ConfigurableBeanFactory) registry).getApplicationStartup(); } - if (DefaultListableBeanFactory.class.isInstance(registry)) { - return ((DefaultListableBeanFactory)registry).getBeanProvider(ApplicationStartup.class).getIfAvailable(() -> ApplicationStartup.DEFAULT); - } - - if(GenericApplicationContext.class.isInstance(registry)) { - return ((GenericApplicationContext)registry).getDefaultListableBeanFactory().getApplicationStartup(); + if (registry instanceof GenericApplicationContext) { + return ((GenericApplicationContext) registry).getDefaultListableBeanFactory().getApplicationStartup(); } return ApplicationStartup.DEFAULT;