From 5da6cf20a2e0cb1aea24000c2343cc88688d8a9a Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Thu, 9 Aug 2018 08:15:11 +0200 Subject: [PATCH] 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. --- ...notationRepositoryConfigurationSource.java | 15 + .../data/repository/config/BootstrapMode.java | 45 +++ .../DefaultRepositoryConfiguration.java | 2 +- ...erredRepositoryInitializationListener.java | 62 ++++ .../RepositoryBeanDefinitionBuilder.java | 1 + .../RepositoryConfigurationDelegate.java | 108 +++++++ ...positoryConfigurationExtensionSupport.java | 74 ++++- .../config/RepositoryConfigurationSource.java | 8 + .../XmlRepositoryConfigurationSource.java | 16 ++ src/main/resources/META-INF/spring.schemas | 3 +- .../config/spring-repository-2.1.xsd | 272 ++++++++++++++++++ ...faultRepositoryConfigurationUnitTests.java | 6 + .../repository/config/EnableRepositories.java | 2 + .../RepositoryComponentProviderUnitTests.java | 2 +- ...ositoryConfigurationDelegateUnitTests.java | 60 +++- .../repository/sample/AddressRepository.java | 27 ++ .../sample/AddressRepositoryClient.java | 31 ++ 17 files changed, 728 insertions(+), 6 deletions(-) create mode 100644 src/main/java/org/springframework/data/repository/config/BootstrapMode.java create mode 100644 src/main/java/org/springframework/data/repository/config/DeferredRepositoryInitializationListener.java create mode 100644 src/main/resources/org/springframework/data/repository/config/spring-repository-2.1.xsd create mode 100644 src/test/java/org/springframework/data/repository/sample/AddressRepository.java create mode 100644 src/test/java/org/springframework/data/repository/sample/AddressRepositoryClient.java diff --git a/src/main/java/org/springframework/data/repository/config/AnnotationRepositoryConfigurationSource.java b/src/main/java/org/springframework/data/repository/config/AnnotationRepositoryConfigurationSource.java index 5eedf8d7b..5dae4ca0d 100644 --- a/src/main/java/org/springframework/data/repository/config/AnnotationRepositoryConfigurationSource.java +++ b/src/main/java/org/springframework/data/repository/config/AnnotationRepositoryConfigurationSource.java @@ -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. diff --git a/src/main/java/org/springframework/data/repository/config/BootstrapMode.java b/src/main/java/org/springframework/data/repository/config/BootstrapMode.java new file mode 100644 index 000000000..a20fc06e1 --- /dev/null +++ b/src/main/java/org/springframework/data/repository/config/BootstrapMode.java @@ -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; +} diff --git a/src/main/java/org/springframework/data/repository/config/DefaultRepositoryConfiguration.java b/src/main/java/org/springframework/data/repository/config/DefaultRepositoryConfiguration.java index 2c9995515..9083a46ff 100644 --- a/src/main/java/org/springframework/data/repository/config/DefaultRepositoryConfiguration.java +++ b/src/main/java/org/springframework/data/repository/config/DefaultRepositoryConfiguration.java @@ -170,7 +170,7 @@ public class DefaultRepositoryConfiguration, 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; + } +} diff --git a/src/main/java/org/springframework/data/repository/config/RepositoryBeanDefinitionBuilder.java b/src/main/java/org/springframework/data/repository/config/RepositoryBeanDefinitionBuilder.java index 6169afb3d..c1d22b54b 100644 --- a/src/main/java/org/springframework/data/repository/config/RepositoryBeanDefinitionBuilder.java +++ b/src/main/java/org/springframework/data/repository/config/RepositoryBeanDefinitionBuilder.java @@ -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)); 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 0eec3260b..080d45318 100644 --- a/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationDelegate.java +++ b/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationDelegate.java @@ -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> configurations = new HashMap<>(); + for (RepositoryConfiguration 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> 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> 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> configurations) { + + Map> 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; + } + } } diff --git a/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationExtensionSupport.java b/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationExtensionSupport.java index 1cd82b217..b268eafcb 100644 --- a/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationExtensionSupport.java +++ b/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationExtensionSupport.java @@ -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 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 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. diff --git a/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationSource.java b/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationSource.java index 9b5ef4206..7d7e2f807 100644 --- a/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationSource.java +++ b/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationSource.java @@ -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(); } diff --git a/src/main/java/org/springframework/data/repository/config/XmlRepositoryConfigurationSource.java b/src/main/java/org/springframework/data/repository/config/XmlRepositoryConfigurationSource.java index d52b089ab..4f35315e2 100644 --- a/src/main/java/org/springframework/data/repository/config/XmlRepositoryConfigurationSource.java +++ b/src/main/java/org/springframework/data/repository/config/XmlRepositoryConfigurationSource.java @@ -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; + } } diff --git a/src/main/resources/META-INF/spring.schemas b/src/main/resources/META-INF/spring.schemas index d3797779e..85e312e67 100644 --- a/src/main/resources/META-INF/spring.schemas +++ b/src/main/resources/META-INF/spring.schemas @@ -5,4 +5,5 @@ http\://www.springframework.org/schema/data/repository/spring-repository-1.6.xsd http\://www.springframework.org/schema/data/repository/spring-repository-1.7.xsd=org/springframework/data/repository/config/spring-repository-1.7.xsd http\://www.springframework.org/schema/data/repository/spring-repository-1.8.xsd=org/springframework/data/repository/config/spring-repository-1.8.xsd http\://www.springframework.org/schema/data/repository/spring-repository-1.11.xsd=org/springframework/data/repository/config/spring-repository-1.11.xsd -http\://www.springframework.org/schema/data/repository/spring-repository.xsd=org/springframework/data/repository/config/spring-repository-1.11.xsd +http\://www.springframework.org/schema/data/repository/spring-repository-2.1.xsd=org/springframework/data/repository/config/spring-repository-2.1.xsd +http\://www.springframework.org/schema/data/repository/spring-repository.xsd=org/springframework/data/repository/config/spring-repository-2.1.xsd diff --git a/src/main/resources/org/springframework/data/repository/config/spring-repository-2.1.xsd b/src/main/resources/org/springframework/data/repository/config/spring-repository-2.1.xsd new file mode 100644 index 000000000..ac27cee32 --- /dev/null +++ b/src/main/resources/org/springframework/data/repository/config/spring-repository-2.1.xsd @@ -0,0 +1,272 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Where to find the files to read the objects from the repository shall be populated with. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Deprecated as of version 1.11. Configure a dedicated base-class instead. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/java/org/springframework/data/repository/config/DefaultRepositoryConfigurationUnitTests.java b/src/test/java/org/springframework/data/repository/config/DefaultRepositoryConfigurationUnitTests.java index 0de15c311..d650e483e 100755 --- a/src/test/java/org/springframework/data/repository/config/DefaultRepositoryConfigurationUnitTests.java +++ b/src/test/java/org/springframework/data/repository/config/DefaultRepositoryConfigurationUnitTests.java @@ -23,6 +23,7 @@ import lombok.Value; import java.util.Optional; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -45,6 +46,11 @@ public class DefaultRepositoryConfigurationUnitTests { RepositoryConfigurationExtension extension = new SimplerRepositoryConfigurationExtension("factory", "module"); + @Before + public void before() { + when(source.getBootstrapMode()).thenReturn(BootstrapMode.DEFAULT); + } + @Test public void supportsBasicConfiguration() { diff --git a/src/test/java/org/springframework/data/repository/config/EnableRepositories.java b/src/test/java/org/springframework/data/repository/config/EnableRepositories.java index 6d54eae95..6ef010c38 100644 --- a/src/test/java/org/springframework/data/repository/config/EnableRepositories.java +++ b/src/test/java/org/springframework/data/repository/config/EnableRepositories.java @@ -51,4 +51,6 @@ public @interface EnableRepositories { boolean considerNestedRepositories() default false; boolean limitImplementationBasePackages() default true; + + BootstrapMode bootstrapMode() default BootstrapMode.DEFAULT; } diff --git a/src/test/java/org/springframework/data/repository/config/RepositoryComponentProviderUnitTests.java b/src/test/java/org/springframework/data/repository/config/RepositoryComponentProviderUnitTests.java index d4968e7f2..ea67d85ed 100755 --- a/src/test/java/org/springframework/data/repository/config/RepositoryComponentProviderUnitTests.java +++ b/src/test/java/org/springframework/data/repository/config/RepositoryComponentProviderUnitTests.java @@ -49,7 +49,7 @@ public class RepositoryComponentProviderUnitTests { RepositoryComponentProvider provider = new RepositoryComponentProvider(Collections.emptyList(), registry); Set components = provider.findCandidateComponents("org.springframework.data.repository.sample"); - assertThat(components).hasSize(3); + assertThat(components).hasSize(4); assertThat(components).extracting(BeanDefinition::getBeanClassName) .contains(SampleAnnotatedRepository.class.getName()); } 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 1ffef6723..fe96f23a7 100644 --- a/src/test/java/org/springframework/data/repository/config/RepositoryConfigurationDelegateUnitTests.java +++ b/src/test/java/org/springframework/data/repository/config/RepositoryConfigurationDelegateUnitTests.java @@ -15,18 +15,29 @@ */ package org.springframework.data.repository.config; +import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.CoreMatchers.*; -import static org.junit.Assert.*; +import static org.junit.Assert.assertThat; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.aop.TargetSource; +import org.springframework.aop.framework.Advised; +import org.springframework.beans.factory.ListableBeanFactory; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.parsing.BeanComponentDefinition; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.ComponentScan.Filter; +import org.springframework.context.annotation.FilterType; import org.springframework.context.support.GenericApplicationContext; import org.springframework.core.env.StandardEnvironment; import org.springframework.core.type.StandardAnnotationMetadata; +import org.springframework.data.repository.config.RepositoryConfigurationDelegate.LazyRepositoryInjectionPointResolver; +import org.springframework.data.repository.sample.AddressRepository; +import org.springframework.data.repository.sample.AddressRepositoryClient; import org.springframework.data.repository.sample.ProductRepository; /** @@ -35,7 +46,7 @@ import org.springframework.data.repository.sample.ProductRepository; * @author Oliver Gierke * @soundtrack Richard Spaven - Tribute (Whole Other*) */ -@RunWith(MockitoJUnitRunner.class) +@RunWith(MockitoJUnitRunner.Silent.class) public class RepositoryConfigurationDelegateUnitTests { @Mock RepositoryConfigurationExtension extension; @@ -61,6 +72,51 @@ public class RepositoryConfigurationDelegateUnitTests { } } + @Test // DATACMNS-1368 + public void registersLazyAutowireCandidateResolver() { + assertLazyRepositoryBeanSetup(LazyConfig.class); + } + + @Test // DATACMNS-1368 + public void registersDeferredRepositoryInitializationListener() { + + ListableBeanFactory beanFactory = assertLazyRepositoryBeanSetup(DeferredConfig.class); + + assertThat(beanFactory.getBeanNamesForType(DeferredRepositoryInitializationListener.class)).isNotEmpty(); + + } + + private static ListableBeanFactory assertLazyRepositoryBeanSetup(Class configClass) { + + StandardEnvironment environment = new StandardEnvironment(); + AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(configClass); + + assertThat(context.getDefaultListableBeanFactory().getAutowireCandidateResolver()) + .isInstanceOf(LazyRepositoryInjectionPointResolver.class); + + AddressRepositoryClient client = context.getBean(AddressRepositoryClient.class); + AddressRepository repository = client.getRepository(); + + assertThat(Advised.class.isInstance(repository)).isTrue(); + + TargetSource targetSource = Advised.class.cast(repository).getTargetSource(); + assertThat(targetSource).isNotNull(); + + return context.getDefaultListableBeanFactory(); + } + @EnableRepositories(basePackageClasses = ProductRepository.class) static class TestConfig {} + + @ComponentScan(basePackageClasses = AddressRepository.class) + @EnableRepositories(basePackageClasses = AddressRepository.class, + includeFilters = @Filter(type = FilterType.ASSIGNABLE_TYPE, classes = AddressRepository.class), + bootstrapMode = BootstrapMode.LAZY) + static class LazyConfig {} + + @ComponentScan(basePackageClasses = AddressRepository.class) + @EnableRepositories(basePackageClasses = AddressRepository.class, + includeFilters = @Filter(type = FilterType.ASSIGNABLE_TYPE, classes = AddressRepository.class), + bootstrapMode = BootstrapMode.DEFERRED) + static class DeferredConfig {} } diff --git a/src/test/java/org/springframework/data/repository/sample/AddressRepository.java b/src/test/java/org/springframework/data/repository/sample/AddressRepository.java new file mode 100644 index 000000000..feaffbbf7 --- /dev/null +++ b/src/test/java/org/springframework/data/repository/sample/AddressRepository.java @@ -0,0 +1,27 @@ +/* + * 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.sample; + +import org.springframework.data.repository.Repository; +import org.springframework.data.repository.sample.AddressRepository.Address; + +/** + * @author Oliver Gierke + */ +public interface AddressRepository extends Repository { + + static class Address {} +} diff --git a/src/test/java/org/springframework/data/repository/sample/AddressRepositoryClient.java b/src/test/java/org/springframework/data/repository/sample/AddressRepositoryClient.java new file mode 100644 index 000000000..237733d82 --- /dev/null +++ b/src/test/java/org/springframework/data/repository/sample/AddressRepositoryClient.java @@ -0,0 +1,31 @@ +/* + * 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.sample; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import org.springframework.stereotype.Component; + +/** + * @author Oliver Gierke + */ +@Component +@RequiredArgsConstructor +public class AddressRepositoryClient { + + private final @Getter AddressRepository repository; +}