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.
This commit is contained in:
Christoph Strobl
2020-11-17 14:38:33 +01:00
committed by Mark Paluch
parent 584464807e
commit 7d7f62f703
6 changed files with 151 additions and 6 deletions

View File

@@ -25,7 +25,7 @@ import java.util.stream.Collectors;
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.BeanDefinitionBuilder;
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 class RepositoryConfigurationDelegate {
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<RepositoryConfiguration<RepositoryConfigurationSource>> configurations = extension
@@ -184,6 +192,9 @@ public class RepositoryConfigurationDelegate {
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 @@ public class RepositoryConfigurationDelegate {
}
DefaultListableBeanFactory beanFactory = DefaultListableBeanFactory.class.cast(registry);
AutowireCandidateResolver resolver = beanFactory.getAutowireCandidateResolver();
if (!Arrays.asList(ContextAnnotationAutowireCandidateResolver.class, LazyRepositoryInjectionPointResolver.class)
@@ -253,6 +263,23 @@ public class RepositoryConfigurationDelegate {
return multipleModulesFound;
}
ApplicationStartup getStartup(BeanDefinitionRegistry registry) {
if(ConfigurableBeanFactory.class.isInstance(registry)) {
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();
}
return ApplicationStartup.DEFAULT;
}
/**
* Customer {@link ContextAnnotationAutowireCandidateResolver} that also considers all injection points for lazy
* repositories lazy.

View File

@@ -364,6 +364,7 @@ public class RepositoryComposition {
* Value object representing an ordered list of {@link RepositoryFragment fragments}.
*
* @author Mark Paluch
* @author Christoph Strobl
*/
public static class RepositoryFragments implements Streamable<RepositoryFragment<?>> {
@@ -550,6 +551,16 @@ public class RepositoryComposition {
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()

View File

@@ -35,9 +35,12 @@ import org.springframework.beans.BeansException;
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,30 @@ public abstract class RepositoryFactorySupport implements BeanClassLoaderAware,
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");
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();
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);
@@ -291,31 +309,56 @@ public abstract class RepositoryFactorySupport implements BeanClassLoaderAware,
result.addAdvisor(ExposeInvocationInterceptor.ADVISOR);
postProcessors.forEach(processor -> processor.postProcess(result, information));
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 (DefaultMethodInvokingMethodInterceptor.hasDefaultMethods(repositoryInterface)) {
result.addAdvice(new DefaultMethodInvokingMethodInterceptor());
}
StartupStep queryExecutorsStep = applicationStartup.start("spring.data.repository.queryexecutors");
ProjectionFactory projectionFactory = getProjectionFactory(classLoader, beanFactory);
Optional<QueryLookupStrategy> 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()));
logger.debug(LogMessage.format("Finished creation of repository instance for {}.",
repositoryInterface.getName()));
}
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.
*