Skip to content

GH-2247 - Add repository initialization metrics. #2273

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>org.springframework.data</groupId>
<artifactId>spring-data-commons</artifactId>
<version>2.5.0-SNAPSHOT</version>
<version>2.5.0-DATACMNS-1832</version>

<name>Spring Data Core</name>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -54,6 +57,7 @@
* @author Oliver Gierke
* @author Jens Schauder
* @author Mark Paluch
* @author Christoph Strobl
*/
public class RepositoryConfigurationDelegate {

Expand Down Expand Up @@ -143,6 +147,12 @@ public List<BeanComponentDefinition> 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<RepositoryConfiguration<RepositoryConfigurationSource>> configurations = extension
Expand Down Expand Up @@ -184,6 +194,9 @@ public List<BeanComponentDefinition> 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()));
Expand All @@ -208,7 +221,6 @@ private static void potentiallyLazifyRepositories(Map<String, RepositoryConfigur
}

DefaultListableBeanFactory beanFactory = DefaultListableBeanFactory.class.cast(registry);

AutowireCandidateResolver resolver = beanFactory.getAutowireCandidateResolver();

if (!Arrays.asList(ContextAnnotationAutowireCandidateResolver.class, LazyRepositoryInjectionPointResolver.class)
Expand Down Expand Up @@ -253,6 +265,19 @@ private boolean multipleStoresDetected() {
return multipleModulesFound;
}

private static ApplicationStartup getStartup(BeanDefinitionRegistry registry) {

if (registry instanceof ConfigurableBeanFactory) {
return ((ConfigurableBeanFactory) registry).getApplicationStartup();
}

if (registry instanceof GenericApplicationContext) {
return ((GenericApplicationContext) registry).getDefaultListableBeanFactory().getApplicationStartup();
}

return ApplicationStartup.DEFAULT;
}

/**
* Customer {@link ContextAnnotationAutowireCandidateResolver} that also considers all injection points for lazy
* repositories lazy.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,7 @@ public BiFunction<Method, Object[], Object[]> getArgumentConverter() {
* Value object representing an ordered list of {@link RepositoryFragment fragments}.
*
* @author Mark Paluch
* @author Christoph Strobl
*/
public static class RepositoryFragments implements Streamable<RepositoryFragment<?>> {

Expand Down Expand Up @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -272,15 +275,54 @@ public <T> T getRepository(Class<T> 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);
Expand All @@ -291,7 +333,19 @@ public <T> T getRepository(Class<T> 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());
Expand All @@ -303,14 +357,17 @@ public <T> T getRepository(Class<T> 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;
Expand Down Expand Up @@ -497,6 +554,25 @@ protected final <R> 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}.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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 {}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,32 +19,40 @@

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;
import org.springframework.data.repository.core.NamedQueries;
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;

/**
* Dummy implementation for {@link RepositoryFactorySupport} that is equipped with mocks to simulate behavior for test
* cases.
*
* @author Oliver Gierke
* @author Christoph Strobl
*/
public class DummyRepositoryFactory extends RepositoryFactorySupport {

public final MyRepositoryQuery queryOne = mock(MyRepositoryQuery.class);
public final RepositoryQuery queryTwo = mock(RepositoryQuery.class);
public final QueryLookupStrategy strategy = mock(QueryLookupStrategy.class);

private final ApplicationStartup applicationStartup;

@SuppressWarnings("unchecked") private final QuerydslPredicateExecutor<Object> querydsl = mock(
QuerydslPredicateExecutor.class);
private final Object repository;
Expand All @@ -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.<Supplier<String>> any())).thenReturn(startupStep);

BeanFactory beanFactory = Mockito.mock(BeanFactory.class);
when(beanFactory.getBean(ApplicationStartup.class)).thenReturn(applicationStartup);
setBeanFactory(beanFactory);
}

/*
Expand Down Expand Up @@ -109,10 +127,15 @@ protected RepositoryFragments getRepositoryFragments(RepositoryMetadata metadata
: fragments;
}

ApplicationStartup getApplicationStartup() {
return this.applicationStartup;
}

/**
* @author Mark Paluch
*/
public interface MyRepositoryQuery extends RepositoryQuery {

}

}
Loading