DATACMNS-1368 - Add support for deferred repository initialization.
Both the XML and annotation based configuration sources now support a bootstrap mode configuration property that allow to configure whether repositories are eagerly initialized unless declared as lazy, initialized in a deferred way (just before the application context finishs bootstrapping) or entirely lazy (upon first usage, i.e. a method invocation). We now register a custom AutowireCandidateResolver that will consider all injection points of lazy repositories lazy too. This will prevent them to accidentally trigger downstream infrastructure initialization.
This commit is contained in:
@@ -65,6 +65,7 @@ public class AnnotationRepositoryConfigurationSource extends RepositoryConfigura
|
||||
private static final String REPOSITORY_FACTORY_BEAN_CLASS = "repositoryFactoryBeanClass";
|
||||
private static final String REPOSITORY_BASE_CLASS = "repositoryBaseClass";
|
||||
private static final String CONSIDER_NESTED_REPOSITORIES = "considerNestedRepositories";
|
||||
private static final String BOOTSTRAP_MODE = "bootstrapMode";
|
||||
|
||||
private final AnnotationMetadata configMetadata;
|
||||
private final AnnotationMetadata enableAnnotationMetadata;
|
||||
@@ -341,6 +342,20 @@ public class AnnotationRepositoryConfigurationSource extends RepositoryConfigura
|
||||
return hasExplicitFilters;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.config.RepositoryConfigurationSource#getBootstrapMode()
|
||||
*/
|
||||
@Override
|
||||
public BootstrapMode getBootstrapMode() {
|
||||
|
||||
try {
|
||||
return attributes.getEnum(BOOTSTRAP_MODE);
|
||||
} catch (IllegalArgumentException o_O) {
|
||||
return BootstrapMode.DEFAULT;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely reads the {@code pattern} attribute from the given {@link AnnotationAttributes} and returns an empty list if
|
||||
* the attribute is not present.
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.repository.config;
|
||||
|
||||
/**
|
||||
* Enumeration to define in which way repositories are bootstrapped.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @see RepositoryConfigurationSource#getBootstrapMode()
|
||||
* @since 2.1
|
||||
* @soundtrack Dave Matthews Band - She (Come Tomorrow)
|
||||
*/
|
||||
public enum BootstrapMode {
|
||||
|
||||
/**
|
||||
* Repository proxies are instantiated eagerly, just like any other Spring bean, except explicitly marked as lazy.
|
||||
* Thus, injection into repository clients will trigger initialization.
|
||||
*/
|
||||
DEFAULT,
|
||||
|
||||
/**
|
||||
* Repository bean definitions are considered lazy and clients will get repository proxies injected that will
|
||||
* initialize on first access. Repository initialization is triggered on application context bootstrap completion.
|
||||
*/
|
||||
DEFERRED,
|
||||
|
||||
/**
|
||||
* Repository bean definitions are considered lazy, lazily inject and only initialized on first use, i.e. the
|
||||
* application might have fully started without the repositories initialized.
|
||||
*/
|
||||
LAZY;
|
||||
}
|
||||
@@ -170,7 +170,7 @@ public class DefaultRepositoryConfiguration<T extends RepositoryConfigurationSou
|
||||
*/
|
||||
@Override
|
||||
public boolean isLazyInit() {
|
||||
return definition.isLazyInit();
|
||||
return definition.isLazyInit() || !configurationSource.getBootstrapMode().equals(BootstrapMode.DEFAULT);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2018 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.data.repository.config;
|
||||
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.springframework.beans.factory.ListableBeanFactory;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.event.ContextRefreshedEvent;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.data.repository.Repository;
|
||||
|
||||
/**
|
||||
* {@link ApplicationListener} to trigger the initialization of Spring Data repositories right before the application
|
||||
* context is started.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @since 2.1
|
||||
* @soundtrack Dave Matthews Band - Here On Out (Come Tomorrow)
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
class DeferredRepositoryInitializationListener implements ApplicationListener<ContextRefreshedEvent>, Ordered {
|
||||
|
||||
private final @NonNull ListableBeanFactory beanFactory;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.context.ApplicationListener#onApplicationEvent(org.springframework.context.ApplicationEvent)
|
||||
*/
|
||||
@Override
|
||||
public void onApplicationEvent(ContextRefreshedEvent event) {
|
||||
|
||||
LOG.debug("Triggering deferred initialization of Spring Data repositories…");
|
||||
|
||||
beanFactory.getBeansOfType(Repository.class);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.core.Ordered#getOrder()
|
||||
*/
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return Ordered.HIGHEST_PRECEDENCE;
|
||||
}
|
||||
}
|
||||
@@ -101,6 +101,7 @@ class RepositoryBeanDefinitionBuilder {
|
||||
builder.addConstructorArgValue(configuration.getRepositoryInterface());
|
||||
builder.addPropertyValue("queryLookupStrategyKey", configuration.getQueryLookupStrategyKey());
|
||||
builder.addPropertyValue("lazyInit", configuration.isLazyInit());
|
||||
builder.setLazyInit(configuration.isLazyInit());
|
||||
|
||||
configuration.getRepositoryBaseClassName()//
|
||||
.ifPresent(it -> builder.addPropertyValue("repositoryBaseClass", it));
|
||||
|
||||
@@ -15,18 +15,26 @@
|
||||
*/
|
||||
package org.springframework.data.repository.config;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import org.springframework.beans.factory.config.DependencyDescriptor;
|
||||
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
|
||||
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||
import org.springframework.beans.factory.support.AutowireCandidateResolver;
|
||||
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.core.env.Environment;
|
||||
import org.springframework.core.env.EnvironmentCapable;
|
||||
import org.springframework.core.env.StandardEnvironment;
|
||||
@@ -49,6 +57,7 @@ public class RepositoryConfigurationDelegate {
|
||||
|
||||
private static final String REPOSITORY_REGISTRATION = "Spring Data {} - Registering repository: {} - Interface: {} - Factory: {}";
|
||||
private static final String MULTIPLE_MODULES = "Multiple Spring Data modules found, entering strict repository configuration mode!";
|
||||
private static final String NON_DEFAULT_AUTOWIRE_CANDIDATE_RESOLVER = "Non-default AutowireCandidateResolver ({}) detected. Skipping the registration of LazyRepositoryInjectionPointResolver. Lazy repository injection will not be working!";
|
||||
|
||||
static final String FACTORY_BEAN_OBJECT_TYPE = "factoryBeanObjectType";
|
||||
|
||||
@@ -122,9 +131,13 @@ public class RepositoryConfigurationDelegate {
|
||||
configurationSource.getBasePackages().stream().collect(Collectors.joining(", ")));
|
||||
}
|
||||
|
||||
Map<String, RepositoryConfiguration<?>> configurations = new HashMap<>();
|
||||
|
||||
for (RepositoryConfiguration<? extends RepositoryConfigurationSource> configuration : extension
|
||||
.getRepositoryConfigurations(configurationSource, resourceLoader, inMultiStoreMode)) {
|
||||
|
||||
configurations.put(configuration.getRepositoryInterface(), configuration);
|
||||
|
||||
BeanDefinitionBuilder definitionBuilder = builder.build(configuration);
|
||||
|
||||
extension.postProcess(definitionBuilder, configurationSource);
|
||||
@@ -149,6 +162,8 @@ public class RepositoryConfigurationDelegate {
|
||||
definitions.add(new BeanComponentDefinition(beanDefinition, beanName));
|
||||
}
|
||||
|
||||
potentiallyLazifyRepositories(configurations, registry, configurationSource.getBootstrapMode());
|
||||
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Finished repository scanning.");
|
||||
}
|
||||
@@ -156,6 +171,45 @@ public class RepositoryConfigurationDelegate {
|
||||
return definitions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a {@link LazyRepositoryInjectionPointResolver} over the default
|
||||
* {@link ContextAnnotationAutowireCandidateResolver} to make injection points of lazy repositories lazy, too. Will
|
||||
* augment the {@link LazyRepositoryInjectionPointResolver}'s configuration if there already is one configured.
|
||||
*
|
||||
* @param configurations must not be {@literal null}.
|
||||
* @param registry must not be {@literal null}.
|
||||
*/
|
||||
private static void potentiallyLazifyRepositories(Map<String, RepositoryConfiguration<?>> configurations,
|
||||
BeanDefinitionRegistry registry, BootstrapMode mode) {
|
||||
|
||||
if (!DefaultListableBeanFactory.class.isInstance(registry) || mode.equals(BootstrapMode.DEFAULT)) {
|
||||
return;
|
||||
}
|
||||
|
||||
DefaultListableBeanFactory beanFactory = DefaultListableBeanFactory.class.cast(registry);
|
||||
|
||||
AutowireCandidateResolver resolver = beanFactory.getAutowireCandidateResolver();
|
||||
|
||||
if (!Arrays.asList(ContextAnnotationAutowireCandidateResolver.class, LazyRepositoryInjectionPointResolver.class)
|
||||
.contains(resolver.getClass())) {
|
||||
|
||||
LOG.warn(NON_DEFAULT_AUTOWIRE_CANDIDATE_RESOLVER, resolver.getClass().getName());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
AutowireCandidateResolver newResolver = LazyRepositoryInjectionPointResolver.class.isInstance(resolver) //
|
||||
? LazyRepositoryInjectionPointResolver.class.cast(resolver).withAdditionalConfigurations(configurations) //
|
||||
: new LazyRepositoryInjectionPointResolver(configurations);
|
||||
|
||||
beanFactory.setAutowireCandidateResolver(newResolver);
|
||||
|
||||
if (mode.equals(BootstrapMode.DEFERRED)) {
|
||||
beanFactory.registerSingleton(DeferredRepositoryInitializationListener.class.getName(),
|
||||
new DeferredRepositoryInitializationListener(beanFactory));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans {@code repository.support} packages for implementations of {@link RepositoryFactorySupport}. Finding more
|
||||
* than a single type is considered a multi-store configuration scenario which will trigger stricter repository
|
||||
@@ -174,4 +228,58 @@ public class RepositoryConfigurationDelegate {
|
||||
|
||||
return multipleModulesFound;
|
||||
}
|
||||
|
||||
/**
|
||||
* Customer {@link ContextAnnotationAutowireCandidateResolver} that also considers all injection points for lazy
|
||||
* repositories lazy.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @since 2.1
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
static class LazyRepositoryInjectionPointResolver extends ContextAnnotationAutowireCandidateResolver {
|
||||
|
||||
private final Map<String, RepositoryConfiguration<?>> configurations;
|
||||
|
||||
/**
|
||||
* Returns a new {@link LazyRepositoryInjectionPointResolver} that will have its configurations augmented with the
|
||||
* given ones.
|
||||
*
|
||||
* @param configurations must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
LazyRepositoryInjectionPointResolver withAdditionalConfigurations(
|
||||
Map<String, RepositoryConfiguration<?>> configurations) {
|
||||
|
||||
Map<String, RepositoryConfiguration<?>> map = new HashMap<>(this.configurations);
|
||||
map.putAll(configurations);
|
||||
|
||||
return new LazyRepositoryInjectionPointResolver(map);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.context.annotation.ContextAnnotationAutowireCandidateResolver#isLazy(org.springframework.beans.factory.config.DependencyDescriptor)
|
||||
*/
|
||||
@Override
|
||||
protected boolean isLazy(DependencyDescriptor descriptor) {
|
||||
|
||||
Class<?> type = descriptor.getDependencyType();
|
||||
|
||||
RepositoryConfiguration<?> configuration = configurations.get(type.getName());
|
||||
|
||||
if (configuration == null) {
|
||||
return super.isLazy(descriptor);
|
||||
}
|
||||
|
||||
boolean lazyInit = configuration.isLazyInit();
|
||||
|
||||
if (lazyInit) {
|
||||
LOG.debug("Creating lazy injection proxy for {}…", configuration.getRepositoryInterface());
|
||||
}
|
||||
|
||||
return lazyInit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@@ -193,13 +194,17 @@ public abstract class RepositoryConfigurationExtensionSupport implements Reposit
|
||||
* {@link BeanDefinitionRegistry}. For {@link BeanDefinition}s to be registered once-and-only-once for all
|
||||
* configuration elements (annotation or XML), prefer calling
|
||||
* {@link #registerIfNotAlreadyRegistered(AbstractBeanDefinition, BeanDefinitionRegistry, String, Object)} with a
|
||||
* dedicated bean name to avoid the bead definition being registered multiple times. *
|
||||
* dedicated bean name to avoid the bead definition being registered multiple times.
|
||||
*
|
||||
* @param registry must not be {@literal null}.
|
||||
* @param bean must not be {@literal null}.
|
||||
* @param source must not be {@literal null}.
|
||||
* @return the bean name generated for the given {@link BeanDefinition}
|
||||
* @deprecated since 2.1, use
|
||||
* {@link #registerWithSourceAndGeneratedBeanName(AbstractBeanDefinition, BeanDefinitionRegistry, Object)}
|
||||
* instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public static String registerWithSourceAndGeneratedBeanName(BeanDefinitionRegistry registry,
|
||||
AbstractBeanDefinition bean, Object source) {
|
||||
|
||||
@@ -211,6 +216,29 @@ public abstract class RepositoryConfigurationExtensionSupport implements Reposit
|
||||
return beanName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the given source on the given {@link AbstractBeanDefinition} and registers it inside the given
|
||||
* {@link BeanDefinitionRegistry}. For {@link BeanDefinition}s to be registered once-and-only-once for all
|
||||
* configuration elements (annotation or XML), prefer calling
|
||||
* {@link #registerIfNotAlreadyRegistered(AbstractBeanDefinition, BeanDefinitionRegistry, String, Object)} with a
|
||||
* dedicated bean name to avoid the bead definition being registered multiple times.
|
||||
*
|
||||
* @param bean must not be {@literal null}.
|
||||
* @param registry must not be {@literal null}.
|
||||
* @param source must not be {@literal null}.
|
||||
* @return the bean name generated for the given {@link BeanDefinition}
|
||||
*/
|
||||
public static String registerWithSourceAndGeneratedBeanName(AbstractBeanDefinition bean,
|
||||
BeanDefinitionRegistry registry, Object source) {
|
||||
|
||||
bean.setSource(source);
|
||||
|
||||
String beanName = generateBeanName(bean, registry);
|
||||
registry.registerBeanDefinition(beanName, bean);
|
||||
|
||||
return beanName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the given {@link AbstractBeanDefinition} with the given registry with the given bean name unless the
|
||||
* registry already contains a bean with that name.
|
||||
@@ -219,18 +247,62 @@ public abstract class RepositoryConfigurationExtensionSupport implements Reposit
|
||||
* @param registry must not be {@literal null}.
|
||||
* @param beanName must not be {@literal null} or empty.
|
||||
* @param source must not be {@literal null}.
|
||||
* @deprecated since 2.1, prefer
|
||||
* {@link #registerIfNotAlreadyRegistered(Supplier, BeanDefinitionRegistry, String, Object)}
|
||||
*/
|
||||
@Deprecated
|
||||
public static void registerIfNotAlreadyRegistered(AbstractBeanDefinition bean, BeanDefinitionRegistry registry,
|
||||
String beanName, Object source) {
|
||||
registerIfNotAlreadyRegistered(() -> bean, registry, beanName, source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the {@link AbstractBeanDefinition} produced by the given {@link Supplier} with the given registry with
|
||||
* the given bean name unless the registry already contains a bean with that name.
|
||||
*
|
||||
* @param supplier must not be {@literal null}.
|
||||
* @param registry must not be {@literal null}.
|
||||
* @param beanName must not be {@literal null} or empty.
|
||||
* @param source must not be {@literal null}.
|
||||
* @since 2.1
|
||||
*/
|
||||
public static void registerIfNotAlreadyRegistered(Supplier<AbstractBeanDefinition> supplier,
|
||||
BeanDefinitionRegistry registry, String beanName, Object source) {
|
||||
|
||||
if (registry.containsBeanDefinition(beanName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
AbstractBeanDefinition bean = supplier.get();
|
||||
|
||||
bean.setSource(source);
|
||||
registry.registerBeanDefinition(beanName, bean);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the {@link AbstractBeanDefinition} produced by the given {@link Supplier} as lazy bean definition with
|
||||
* the given registry with the given bean name unless the registry already contains a bean with that name.
|
||||
*
|
||||
* @param supplier must not be {@literal null}.
|
||||
* @param registry must not be {@literal null}.
|
||||
* @param beanName must not be {@literal null} or empty.
|
||||
* @param source must not be {@literal null}.
|
||||
* @since 2.1
|
||||
*/
|
||||
public static void registerLazyIfNotAlreadyRegistered(Supplier<AbstractBeanDefinition> supplier,
|
||||
BeanDefinitionRegistry registry, String beanName, Object source) {
|
||||
|
||||
if (registry.containsBeanDefinition(beanName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
AbstractBeanDefinition definition = supplier.get();
|
||||
definition.setSource(source);
|
||||
definition.setLazyInit(true);
|
||||
|
||||
registry.registerBeanDefinition(beanName, definition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given {@link BeanDefinitionRegistry} already contains a bean of the given type assuming the
|
||||
* bean name has been auto-generated.
|
||||
|
||||
@@ -137,4 +137,12 @@ public interface RepositoryConfigurationSource {
|
||||
* @since 2.1
|
||||
*/
|
||||
ImplementationDetectionConfiguration toImplementationDetectionConfiguration(MetadataReaderFactory factory);
|
||||
|
||||
/**
|
||||
* Defines the repository {@link BootstrapMode} to be used.
|
||||
*
|
||||
* @return
|
||||
* @since 2.1
|
||||
*/
|
||||
BootstrapMode getBootstrapMode();
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
package org.springframework.data.repository.config;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Locale;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.beans.factory.xml.ParserContext;
|
||||
@@ -50,6 +51,7 @@ public class XmlRepositoryConfigurationSource extends RepositoryConfigurationSou
|
||||
private static final String REPOSITORY_FACTORY_BEAN_CLASS_NAME = "factory-class";
|
||||
private static final String REPOSITORY_BASE_CLASS_NAME = "base-class";
|
||||
private static final String CONSIDER_NESTED_REPOSITORIES = "consider-nested-repositories";
|
||||
private static final String BOOTSTRAP_MODE = "bootstrap-mode";
|
||||
|
||||
private final Element element;
|
||||
private final ParserContext context;
|
||||
@@ -211,4 +213,18 @@ public class XmlRepositoryConfigurationSource extends RepositoryConfigurationSou
|
||||
public boolean usesExplicitFilters() {
|
||||
return !(this.includeFilters.isEmpty() && this.excludeFilters.isEmpty());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.config.RepositoryConfigurationSource#getBootstrapMode()
|
||||
*/
|
||||
@Override
|
||||
public BootstrapMode getBootstrapMode() {
|
||||
|
||||
String attribute = element.getAttribute(BOOTSTRAP_MODE);
|
||||
|
||||
return StringUtils.hasText(attribute) //
|
||||
? BootstrapMode.valueOf(attribute.toUpperCase(Locale.US)) //
|
||||
: BootstrapMode.DEFAULT;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user