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
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..28f1501e44 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;
@@ -54,6 +57,7 @@
* @author Oliver Gierke
* @author Jens Schauder
* @author Mark Paluch
+ * @author Christoph Strobl
*/
public class RepositoryConfigurationDelegate {
@@ -143,6 +147,12 @@ public List registerRepositoriesIn(BeanDefinitionRegist
configurationSource.getBasePackages().stream().collect(Collectors.joining(", "))));
}
+ ApplicationStartup startup = getStartup(registry);
+ StartupStep repoScan = startup.start("spring.data.repository.scanning");
+ repoScan.tag("dataModule", extension.getModuleName());
+ repoScan.tag("basePackages",
+ () -> configurationSource.getBasePackages().stream().collect(Collectors.joining(", ")));
+
watch.start();
Collection> configurations = extension
@@ -184,6 +194,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 +221,6 @@ private static void potentiallyLazifyRepositories(Map 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 4920ae0eb7..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
@@ -35,9 +35,12 @@
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.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;
@@ -272,15 +275,54 @@ public T getRepository(Class repositoryInterface, RepositoryFragments fra
Assert.notNull(repositoryInterface, "Repository interface must not be null!");
Assert.notNull(fragments, "RepositoryFragments must not be null!");
+ ApplicationStartup applicationStartup = getStartup();
+
+ StartupStep repositoryInit = onEvent(applicationStartup, "spring.data.repository.init", repositoryInterface);
+
+ 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 = 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 = onEvent(applicationStartup, "spring.data.repository.target",
+ repositoryInterface);
Object target = getTargetRepository(information);
+ repositoryTargetStep.tag("target", target.getClass().getName());
+ repositoryTargetStep.end();
+
// Create proxy
+ StartupStep repositoryProxyStep = onEvent(applicationStartup, "spring.data.repository.proxy", repositoryInterface);
ProxyFactory result = new ProxyFactory();
result.setTarget(target);
result.setInterfaces(repositoryInterface, Repository.class, TransactionalProxy.class);
@@ -291,7 +333,19 @@ public T getRepository(Class repositoryInterface, RepositoryFragments fra
result.addAdvisor(ExposeInvocationInterceptor.ADVISOR);
- postProcessors.forEach(processor -> processor.postProcess(result, information));
+ 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());
@@ -303,14 +357,17 @@ public T getRepository(Class repositoryInterface, RepositoryFragments fra
result.addAdvice(new QueryExecutorMethodInterceptor(information, projectionFactory, queryLookupStrategy,
namedQueries, queryPostProcessors, methodInvocationListeners));
- 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();
+ repositoryInit.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()));
}
return repository;
@@ -497,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}.
*
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 {}
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