diff --git a/pom.xml b/pom.xml
index 75f26f703f..23d7707b15 100644
--- a/pom.xml
+++ b/pom.xml
@@ -7,7 +7,7 @@
org.springframework.data
spring-data-commons
- 3.0.0-SNAPSHOT
+ 3.0.0-GH-2487-SNAPSHOT
Spring Data Core
Core Spring concepts underpinning every Spring Data module.
diff --git a/src/main/java/org/springframework/data/repository/config/CustomRepositoryImplementationDetector.java b/src/main/java/org/springframework/data/repository/config/CustomRepositoryImplementationDetector.java
index 7be0197916..dc105616a4 100644
--- a/src/main/java/org/springframework/data/repository/config/CustomRepositoryImplementationDetector.java
+++ b/src/main/java/org/springframework/data/repository/config/CustomRepositoryImplementationDetector.java
@@ -18,6 +18,7 @@
import java.util.Collection;
import java.util.Optional;
import java.util.Set;
+import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.springframework.beans.factory.config.BeanDefinition;
@@ -107,8 +108,22 @@ public Optional detectCustomImplementation(Implementatio
.filter(lookup::matches) //
.collect(StreamUtils.toUnmodifiableSet());
+ return selectImplementationCandidate(lookup, definitions, () -> {
+
+ if (definitions.isEmpty()) {
+ return Optional.empty();
+ }
+
+ return Optional.of(definitions.iterator().next());
+ });
+ }
+
+ private static Optional selectImplementationCandidate(
+ ImplementationLookupConfiguration lookup, Set definitions,
+ Supplier> fallback) {
+
return SelectionSet //
- .of(definitions, c -> c.isEmpty() ? Optional.empty() : throwAmbiguousCustomImplementationException(c)) //
+ .of(definitions, c -> c.isEmpty() ? fallback.get() : throwAmbiguousCustomImplementationException(c)) //
.filterIfNecessary(lookup::hasMatchingBeanName) //
.uniqueResult() //
.map(AbstractBeanDefinition.class::cast);
diff --git a/src/main/java/org/springframework/data/repository/config/DefaultImplementationLookupConfiguration.java b/src/main/java/org/springframework/data/repository/config/DefaultImplementationLookupConfiguration.java
index 9c7eff7c42..4b5b3e512e 100644
--- a/src/main/java/org/springframework/data/repository/config/DefaultImplementationLookupConfiguration.java
+++ b/src/main/java/org/springframework/data/repository/config/DefaultImplementationLookupConfiguration.java
@@ -15,7 +15,6 @@
*/
package org.springframework.data.repository.config;
-import java.beans.Introspector;
import java.io.IOException;
import org.springframework.beans.factory.config.BeanDefinition;
@@ -42,22 +41,16 @@ class DefaultImplementationLookupConfiguration implements ImplementationLookupCo
private final String interfaceName;
private final String beanName;
- /**
- * Creates a new {@link DefaultImplementationLookupConfiguration} for the given
- * {@link ImplementationDetectionConfiguration} and interface name.
- *
- * @param config must not be {@literal null}.
- * @param interfaceName must not be {@literal null} or empty.
- */
- DefaultImplementationLookupConfiguration(ImplementationDetectionConfiguration config, String interfaceName) {
+ DefaultImplementationLookupConfiguration(ImplementationDetectionConfiguration config, String interfaceName,
+ String beanName) {
Assert.notNull(config, "ImplementationDetectionConfiguration must not be null");
Assert.hasText(interfaceName, "Interface name must not be null or empty");
+ Assert.hasText(beanName, "Bean name must not be null or empty!");
this.config = config;
this.interfaceName = interfaceName;
- this.beanName = Introspector
- .decapitalize(ClassUtils.getShortName(interfaceName).concat(config.getImplementationPostfix()));
+ this.beanName = beanName;
}
@Override
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 623c94f2f8..bf13ab9d71 100644
--- a/src/main/java/org/springframework/data/repository/config/DefaultRepositoryConfiguration.java
+++ b/src/main/java/org/springframework/data/repository/config/DefaultRepositoryConfiguration.java
@@ -22,6 +22,7 @@
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.data.config.ConfigurationUtils;
import org.springframework.data.repository.query.QueryLookupStrategy.Key;
+import org.springframework.data.util.Lazy;
import org.springframework.data.util.Streamable;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
@@ -45,6 +46,7 @@ public class DefaultRepositoryConfiguration beanName;
public DefaultRepositoryConfiguration(T configurationSource, BeanDefinition definition,
RepositoryConfigurationExtension extension) {
@@ -52,6 +54,7 @@ public DefaultRepositoryConfiguration(T configurationSource, BeanDefinition defi
this.configurationSource = configurationSource;
this.definition = definition;
this.extension = extension;
+ this.beanName = Lazy.of(() -> configurationSource.generateBeanName(definition));
}
public String getBeanId() {
@@ -90,8 +93,7 @@ public String getImplementationClassName() {
}
public String getImplementationBeanName() {
- return configurationSource.generateBeanName(definition)
- + configurationSource.getRepositoryImplementationPostfix().orElse("Impl");
+ return beanName.get() + configurationSource.getRepositoryImplementationPostfix().orElse("Impl");
}
@Nullable
@@ -117,6 +119,11 @@ public String getRepositoryFactoryBeanClassName() {
.orElseGet(extension::getRepositoryFactoryBeanClassName);
}
+ @Override
+ public String getRepositoryBeanName() {
+ return beanName.get();
+ }
+
@Override
public boolean isLazyInit() {
return definition.isLazyInit() || !configurationSource.getBootstrapMode().equals(BootstrapMode.DEFAULT);
diff --git a/src/main/java/org/springframework/data/repository/config/ImplementationDetectionConfiguration.java b/src/main/java/org/springframework/data/repository/config/ImplementationDetectionConfiguration.java
index f95323b946..3063a53a84 100644
--- a/src/main/java/org/springframework/data/repository/config/ImplementationDetectionConfiguration.java
+++ b/src/main/java/org/springframework/data/repository/config/ImplementationDetectionConfiguration.java
@@ -90,7 +90,8 @@ default ImplementationLookupConfiguration forFragment(String fragmentInterfaceNa
Assert.hasText(fragmentInterfaceName, "Fragment interface name must not be null or empty");
- return new DefaultImplementationLookupConfiguration(this, fragmentInterfaceName);
+ return new DefaultImplementationLookupConfiguration(this, fragmentInterfaceName,
+ Introspector.decapitalize(ClassUtils.getShortName(fragmentInterfaceName).concat(getImplementationPostfix())));
}
/**
@@ -103,7 +104,8 @@ default ImplementationLookupConfiguration forRepositoryConfiguration(RepositoryC
Assert.notNull(config, "RepositoryConfiguration must not be null");
- return new DefaultImplementationLookupConfiguration(this, config.getRepositoryInterface()) {
+ return new DefaultImplementationLookupConfiguration(this, config.getRepositoryInterface(),
+ config.getImplementationBeanName()) {
@Override
public Streamable getBasePackages() {
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 80695add4d..4af5ae4add 100644
--- a/src/main/java/org/springframework/data/repository/config/RepositoryBeanDefinitionBuilder.java
+++ b/src/main/java/org/springframework/data/repository/config/RepositoryBeanDefinitionBuilder.java
@@ -147,7 +147,7 @@ RepositoryConfigurationAdapter> buildMetadata(RepositoryConfiguration> confi
List repositoryFragmentConfigurationStream = fragmentMetadata
.getFragmentInterfaces(configuration.getRepositoryInterface()) //
- .map(it -> detectRepositoryFragmentConfiguration(it, config)) //
+ .map(it -> detectRepositoryFragmentConfiguration(it, config, configuration)) //
.flatMap(Optionals::toStream).toList();
if (repositoryFragmentConfigurationStream.isEmpty()) {
@@ -183,26 +183,42 @@ private Optional registerCustomImplementation(RepositoryConfiguration>
ImplementationLookupConfiguration lookup = configuration.toLookupConfiguration(metadataReaderFactory);
- String beanName = lookup.getImplementationBeanName();
+ String configurationBeanName = lookup.getImplementationBeanName();
// Already a bean configured?
- if (registry.containsBeanDefinition(beanName)) {
- return Optional.of(beanName);
+ if (registry.containsBeanDefinition(configurationBeanName)) {
+
+ if (logger.isDebugEnabled()) {
+ logger.debug(String.format("Custom repository implementation already registered: %s", configurationBeanName));
+ }
+
+ return Optional.of(configurationBeanName);
}
Optional beanDefinition = implementationDetector.detectCustomImplementation(lookup);
return beanDefinition.map(it -> {
- if (logger.isDebugEnabled()) {
- logger.debug("Registering custom repository implementation: " + lookup.getImplementationBeanName() + " "
- + it.getBeanClassName());
- }
-
+ String scannedBeanName = configuration.getConfigurationSource().generateBeanName(it);
it.setSource(configuration.getSource());
- registry.registerBeanDefinition(beanName, it);
- return beanName;
+ if (registry.containsBeanDefinition(scannedBeanName)) {
+
+ if (logger.isDebugEnabled()) {
+ logger.debug(String.format("Custom repository implementation already registered: %s %s", scannedBeanName,
+ it.getBeanClassName()));
+ }
+ } else {
+
+ if (logger.isDebugEnabled()) {
+ logger.debug(String.format("Registering custom repository implementation: %s %s", scannedBeanName,
+ it.getBeanClassName()));
+ }
+
+ registry.registerBeanDefinition(scannedBeanName, it);
+ }
+
+ return scannedBeanName;
});
}
@@ -232,19 +248,20 @@ private Stream registerRepositoryFragmentsImple
.toImplementationDetectionConfiguration(metadataReaderFactory);
return fragmentMetadata.getFragmentInterfaces(configuration.getRepositoryInterface()) //
- .map(it -> detectRepositoryFragmentConfiguration(it, config)) //
+ .map(it -> detectRepositoryFragmentConfiguration(it, config, configuration)) //
.flatMap(Optionals::toStream) //
.peek(it -> potentiallyRegisterFragmentImplementation(configuration, it)) //
.peek(it -> potentiallyRegisterRepositoryFragment(configuration, it));
}
private Optional detectRepositoryFragmentConfiguration(String fragmentInterface,
- ImplementationDetectionConfiguration config) {
+ ImplementationDetectionConfiguration config, RepositoryConfiguration> configuration) {
ImplementationLookupConfiguration lookup = config.forFragment(fragmentInterface);
Optional beanDefinition = implementationDetector.detectCustomImplementation(lookup);
- return beanDefinition.map(bd -> new RepositoryFragmentConfiguration(fragmentInterface, bd));
+ return beanDefinition.map(bd -> new RepositoryFragmentConfiguration(fragmentInterface, bd,
+ configuration.getConfigurationSource().generateBeanName(bd)));
}
private void potentiallyRegisterFragmentImplementation(RepositoryConfiguration> repositoryConfiguration,
@@ -254,16 +271,21 @@ private void potentiallyRegisterFragmentImplementation(RepositoryConfiguration
// Already a bean configured?
if (registry.containsBeanDefinition(beanName)) {
- return;
- }
- if (logger.isDebugEnabled()) {
- logger.debug(String.format("Registering repository fragment implementation: %s %s", beanName,
- fragmentConfiguration.getClassName()));
+ if (logger.isDebugEnabled()) {
+ logger.debug(String.format("Repository fragment implementation already registered: %s", beanName));
+ }
+
+ return;
}
fragmentConfiguration.getBeanDefinition().ifPresent(bd -> {
+ if (logger.isDebugEnabled()) {
+ logger.debug(String.format("Registering repository fragment implementation: %s %s", beanName,
+ fragmentConfiguration.getClassName()));
+ }
+
bd.setSource(repositoryConfiguration.getSource());
registry.registerBeanDefinition(beanName, bd);
});
@@ -276,11 +298,16 @@ private void potentiallyRegisterRepositoryFragment(RepositoryConfiguration> co
// Already a bean configured?
if (registry.containsBeanDefinition(beanName)) {
+
+ if (logger.isDebugEnabled()) {
+ logger.debug(String.format("RepositoryFragment already registered: %s", beanName));
+ }
+
return;
}
if (logger.isDebugEnabled()) {
- logger.debug("Registering repository fragment: " + beanName);
+ logger.debug(String.format("Registering RepositoryFragment: %s", beanName));
}
BeanDefinitionBuilder fragmentBuilder = BeanDefinitionBuilder.rootBeanDefinition(RepositoryFragment.class,
diff --git a/src/main/java/org/springframework/data/repository/config/RepositoryConfiguration.java b/src/main/java/org/springframework/data/repository/config/RepositoryConfiguration.java
index 48bda60916..35d81f0bde 100644
--- a/src/main/java/org/springframework/data/repository/config/RepositoryConfiguration.java
+++ b/src/main/java/org/springframework/data/repository/config/RepositoryConfiguration.java
@@ -84,6 +84,22 @@ public interface RepositoryConfiguration beanDefinition;
+ private final String beanName;
/**
* Creates a {@link RepositoryFragmentConfiguration} given {@code interfaceName} and {@code className} of the
@@ -45,13 +46,7 @@ public final class RepositoryFragmentConfiguration {
* @param className must not be {@literal null} or empty.
*/
public RepositoryFragmentConfiguration(String interfaceName, String className) {
-
- Assert.hasText(interfaceName, "Interface name must not be null or empty");
- Assert.hasText(className, "Class name must not be null or empty");
-
- this.interfaceName = interfaceName;
- this.className = className;
- this.beanDefinition = Optional.empty();
+ this(interfaceName, className, Optional.empty(), generateBeanName(className));
}
/**
@@ -69,20 +64,40 @@ public RepositoryFragmentConfiguration(String interfaceName, AbstractBeanDefinit
this.interfaceName = interfaceName;
this.className = ConfigurationUtils.getRequiredBeanClassName(beanDefinition);
this.beanDefinition = Optional.of(beanDefinition);
+ this.beanName = generateBeanName();
+ }
+
+ RepositoryFragmentConfiguration(String interfaceName, AbstractBeanDefinition beanDefinition, String beanName) {
+ this(interfaceName, ConfigurationUtils.getRequiredBeanClassName(beanDefinition), Optional.of(beanDefinition),
+ beanName);
}
- public RepositoryFragmentConfiguration(String interfaceName, String className,
- Optional beanDefinition) {
+ private RepositoryFragmentConfiguration(String interfaceName, String className,
+ Optional beanDefinition, String beanName) {
+
+ Assert.hasText(interfaceName, "Interface name must not be null or empty!");
+ Assert.notNull(beanDefinition, "Bean definition must not be null!");
+ Assert.notNull(beanName, "Bean name must not be null!");
+
this.interfaceName = interfaceName;
this.className = className;
this.beanDefinition = beanDefinition;
+ this.beanName = beanName;
+ }
+
+ private String generateBeanName() {
+ return generateBeanName(getClassName());
+ }
+
+ private static String generateBeanName(String className) {
+ return Introspector.decapitalize(ClassUtils.getShortName(className));
}
/**
* @return name of the implementation bean.
*/
public String getImplementationBeanName() {
- return Introspector.decapitalize(ClassUtils.getShortName(getClassName()));
+ return this.beanName;
}
/**
diff --git a/src/test/java/org/springframework/data/repository/config/CustomRepositoryImplementationDetectorUnitTests.java b/src/test/java/org/springframework/data/repository/config/CustomRepositoryImplementationDetectorUnitTests.java
index b272123d2f..6e65c5ce68 100644
--- a/src/test/java/org/springframework/data/repository/config/CustomRepositoryImplementationDetectorUnitTests.java
+++ b/src/test/java/org/springframework/data/repository/config/CustomRepositoryImplementationDetectorUnitTests.java
@@ -19,6 +19,9 @@
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
+import java.beans.Introspector;
+
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Answers;
@@ -36,6 +39,7 @@
* tests {@link CustomRepositoryImplementationDetector}
*
* @author Jens Schauder
+ * @author Mark Paluch
*/
class CustomRepositoryImplementationDetectorUnitTests {
@@ -48,7 +52,8 @@ class CustomRepositoryImplementationDetectorUnitTests {
CustomRepositoryImplementationDetector detector = new CustomRepositoryImplementationDetector(environment,
resourceLoader, configuration);
- {
+ @BeforeEach
+ void setUp() {
when(configuration.forRepositoryConfiguration(any(RepositoryConfiguration.class))).thenCallRealMethod();
when(configuration.getMetadataReaderFactory()).thenReturn(metadataFactory);
when(configuration.getBasePackages()).thenReturn(Streamable.of(this.getClass().getPackage().getName()));
@@ -58,10 +63,10 @@ class CustomRepositoryImplementationDetectorUnitTests {
@Test // DATACMNS-764, DATACMNS-1371
void returnsNullWhenNoImplementationFound() {
- var mock = mock(RepositoryConfiguration.class);
+ RepositoryConfiguration> mock = mock(RepositoryConfiguration.class);
+ when(mock.getImplementationBeanName()).thenReturn("NoImplementationRepositoryImpl");
- var lookup = configuration
- .forRepositoryConfiguration(configFor(NoImplementationRepository.class));
+ var lookup = configuration.forRepositoryConfiguration(configFor(NoImplementationRepository.class));
var beanDefinition = detector.detectCustomImplementation(lookup);
@@ -71,8 +76,7 @@ void returnsNullWhenNoImplementationFound() {
@Test // DATACMNS-764, DATACMNS-1371
void returnsBeanDefinitionWhenOneImplementationIsFound() {
- var lookup = configuration
- .forRepositoryConfiguration(configFor(SingleSampleRepository.class));
+ var lookup = configuration.forRepositoryConfiguration(configFor(SingleSampleRepository.class));
var beanDefinition = detector.detectCustomImplementation(lookup);
@@ -91,8 +95,7 @@ void returnsBeanDefinitionMatchingByNameWhenMultipleImplementationAreFound() {
return className.contains("$First$") ? "canonicalSampleRepositoryTestImpl" : "otherBeanName";
});
- var lookup = configuration
- .forRepositoryConfiguration(configFor(CanonicalSampleRepository.class));
+ var lookup = configuration.forRepositoryConfiguration(configFor(CanonicalSampleRepository.class));
assertThat(detector.detectCustomImplementation(lookup)) //
.hasValueSatisfying(
@@ -113,11 +116,13 @@ void throwsExceptionWhenMultipleImplementationAreFound() {
});
}
- private RepositoryConfiguration configFor(Class> type) {
+ private RepositoryConfiguration> configFor(Class> type) {
RepositoryConfiguration> configuration = mock(RepositoryConfiguration.class);
when(configuration.getRepositoryInterface()).thenReturn(type.getSimpleName());
+ when(configuration.getImplementationBeanName())
+ .thenReturn(Introspector.decapitalize(type.getSimpleName()) + "TestImpl");
when(configuration.getImplementationBasePackages())
.thenReturn(Streamable.of(this.getClass().getPackage().getName()));
diff --git a/src/test/java/org/springframework/data/repository/config/DefaultImplementationLookupConfigurationUnitTests.java b/src/test/java/org/springframework/data/repository/config/DefaultImplementationLookupConfigurationUnitTests.java
index 4da5c73f62..c2eee8dc0e 100644
--- a/src/test/java/org/springframework/data/repository/config/DefaultImplementationLookupConfigurationUnitTests.java
+++ b/src/test/java/org/springframework/data/repository/config/DefaultImplementationLookupConfigurationUnitTests.java
@@ -18,8 +18,14 @@
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
+import java.beans.Introspector;
+
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.support.BeanDefinitionBuilder;
+import org.springframework.util.ClassUtils;
+
/**
* Unit tests for {@link DefaultImplementationLookupConfigurationUnitTests}.
*
@@ -28,11 +34,17 @@
*/
class DefaultImplementationLookupConfigurationUnitTests {
- @Test // DATACMNS-1439
- void shouldConsiderBeanNameDecapitalization() {
+ ImplementationDetectionConfiguration idcMock = mock(ImplementationDetectionConfiguration.class);
- var idcMock = mock(ImplementationDetectionConfiguration.class);
+ @BeforeEach
+ void setUp() {
when(idcMock.getImplementationPostfix()).thenReturn("Impl");
+ when(idcMock.forRepositoryConfiguration(any())).thenCallRealMethod();
+ when(idcMock.forFragment(any())).thenCallRealMethod();
+ }
+
+ @Test // DATACMNS-1439
+ void shouldConsiderBeanNameDecapitalization() {
assertThat(getImplementationBeanName(idcMock, "com.acme.UDPRepository")).isEqualTo("UDPRepositoryImpl");
assertThat(getImplementationBeanName(idcMock, "com.acme.UdpRepository")).isEqualTo("udpRepositoryImpl");
@@ -41,19 +53,20 @@ void shouldConsiderBeanNameDecapitalization() {
@Test // DATACMNS-1754
void shouldUseSimpleClassNameWhenDefiningImplementationNames() {
- var idcMock = mock(ImplementationDetectionConfiguration.class);
- when(idcMock.getImplementationPostfix()).thenReturn("Impl");
-
- var lookupConfiguration = new DefaultImplementationLookupConfiguration(idcMock,
- "com.acme.Repositories$NestedRepository");
+ var lookupConfiguration = idcMock.forFragment("com.acme.Repositories$NestedRepository");
assertThat(lookupConfiguration.getImplementationBeanName()).isEqualTo("repositories.NestedRepositoryImpl");
assertThat(lookupConfiguration.getImplementationClassName()).isEqualTo("NestedRepositoryImpl");
}
private static String getImplementationBeanName(ImplementationDetectionConfiguration idcMock, String interfaceName) {
- var configuration = new DefaultImplementationLookupConfiguration(idcMock,
- interfaceName);
+ var source = mock(RepositoryConfigurationSource.class);
+ when(source.generateBeanName(any())).thenReturn(Introspector.decapitalize(ClassUtils.getShortName(interfaceName)));
+
+ RepositoryConfiguration> repoConfig = new DefaultRepositoryConfiguration<>(source,
+ BeanDefinitionBuilder.rootBeanDefinition(interfaceName).getBeanDefinition(), null);
+
+ var configuration = idcMock.forRepositoryConfiguration(repoConfig);
return configuration.getImplementationBeanName();
}
}
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 a2a1f73e8e..bfbc6ea5e2 100755
--- a/src/test/java/org/springframework/data/repository/config/RepositoryComponentProviderUnitTests.java
+++ b/src/test/java/org/springframework/data/repository/config/RepositoryComponentProviderUnitTests.java
@@ -71,7 +71,7 @@ void shouldConsiderNestedRepositoryInterfacesIfEnabled() {
provider.setConsiderNestedRepositoryInterfaces(true);
var components = provider.findCandidateComponents("org.springframework.data.repository.config");
- var nestedRepositoryClassName = "org.springframework.data.repository.config.RepositoryComponentProviderUnitTests$MyNestedRepository";
+ var nestedRepositoryClassName = "org.springframework.data.repository.config.RepositoryComponentProviderUnitTests$MyNestedRepositoryDefinition";
assertThat(components.size()).isGreaterThanOrEqualTo(1);
assertThat(components).extracting(BeanDefinition::getBeanClassName).contains(nestedRepositoryClassName);
@@ -91,5 +91,5 @@ void exposesBeanDefinitionRegistry() {
assertThat(provider.getRegistry()).isEqualTo(registry);
}
- interface MyNestedRepository extends Repository {}
+ interface MyNestedRepositoryDefinition extends Repository {}
}
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 32747be852..2c5da8ef5b 100644
--- a/src/test/java/org/springframework/data/repository/config/RepositoryConfigurationDelegateUnitTests.java
+++ b/src/test/java/org/springframework/data/repository/config/RepositoryConfigurationDelegateUnitTests.java
@@ -19,14 +19,15 @@
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;
+
import org.springframework.aop.framework.Advised;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.ListableBeanFactory;
+import org.springframework.context.annotation.AnnotationBeanNameGenerator;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
@@ -34,8 +35,15 @@
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.metrics.ApplicationStartup;
+import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.StandardAnnotationMetadata;
import org.springframework.data.repository.config.RepositoryConfigurationDelegate.LazyRepositoryInjectionPointResolver;
+import org.springframework.data.repository.config.annotated.MyAnnotatedRepository;
+import org.springframework.data.repository.config.annotated.MyAnnotatedRepositoryImpl;
+import org.springframework.data.repository.config.annotated.MyFragmentImpl;
+import org.springframework.data.repository.config.excluded.MyOtherRepositoryImpl;
+import org.springframework.data.repository.config.stereotype.MyStereotypeRepository;
+import org.springframework.data.repository.core.support.DummyRepositoryFactoryBean;
import org.springframework.data.repository.sample.AddressRepository;
import org.springframework.data.repository.sample.AddressRepositoryClient;
import org.springframework.data.repository.sample.ProductRepository;
@@ -44,13 +52,14 @@
* Unit tests for {@link RepositoryConfigurationDelegate}.
*
* @author Oliver Gierke
+ * @author Mark Paluch
* @soundtrack Richard Spaven - Tribute (Whole Other*)
*/
@ExtendWith(MockitoExtension.class)
@MockitoSettings(strictness = Strictness.LENIENT)
class RepositoryConfigurationDelegateUnitTests {
- @Mock RepositoryConfigurationExtension extension;
+ RepositoryConfigurationExtension extension = new DummyConfigurationExtension();
@Test // DATACMNS-892
void registersRepositoryBeanNameAsAttribute() {
@@ -68,8 +77,7 @@ void registersRepositoryBeanNameAsAttribute() {
var beanDefinition = definition.getBeanDefinition();
- assertThat(beanDefinition.getAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE).toString())
- .endsWith("Repository");
+ assertThat(beanDefinition.getAttribute(FactoryBean.OBJECT_TYPE_ATTRIBUTE).toString()).endsWith("Repository");
}
}
@@ -87,43 +95,121 @@ void registersDeferredRepositoryInitializationListener() {
}
- private static ListableBeanFactory assertLazyRepositoryBeanSetup(Class> configClass) {
+ @Test // DATACMNS-1832
+ void writesRepositoryScanningMetrics() {
+
+ var startup = Mockito.spy(ApplicationStartup.DEFAULT);
var environment = new StandardEnvironment();
- var context = new AnnotationConfigApplicationContext(configClass);
+ var context = new GenericApplicationContext();
+ context.setApplicationStartup(startup);
- assertThat(context.getDefaultListableBeanFactory().getAutowireCandidateResolver())
- .isInstanceOf(LazyRepositoryInjectionPointResolver.class);
+ RepositoryConfigurationSource configSource = new AnnotationRepositoryConfigurationSource(
+ new StandardAnnotationMetadata(TestConfig.class, true), EnableRepositories.class, context, environment,
+ context.getDefaultListableBeanFactory());
- var client = context.getBean(AddressRepositoryClient.class);
- var repository = client.getRepository();
+ var delegate = new RepositoryConfigurationDelegate(configSource, context, environment);
- assertThat(Advised.class.isInstance(repository)).isTrue();
+ delegate.registerRepositoriesIn(context, extension);
- var targetSource = Advised.class.cast(repository).getTargetSource();
- assertThat(targetSource).isNotNull();
+ Mockito.verify(startup).start("spring.data.repository.scanning");
+ }
- return context.getDefaultListableBeanFactory();
+ @Test // GH-2487
+ void considersDefaultBeanNames() {
+
+ var environment = new StandardEnvironment();
+ var context = new GenericApplicationContext();
+
+ RepositoryConfigurationSource configSource = new AnnotationRepositoryConfigurationSource(
+ AnnotationMetadata.introspect(DefaultBeanNamesConfig.class), EnableRepositories.class, context, environment,
+ context.getDefaultListableBeanFactory(), new AnnotationBeanNameGenerator());
+
+ var delegate = new RepositoryConfigurationDelegate(configSource, context, environment);
+
+ delegate.registerRepositoriesIn(context, extension);
+
+ assertThat(context.getBeanFactory().getBeanDefinition("myOtherRepository")).isNotNull();
+ assertThat(context.getBeanFactory().getBeanDefinition("myOtherRepositoryImpl")).isNotNull();
}
- @Test // DATACMNS-1832
- void writesRepositoryScanningMetrics() {
+ @Test // GH-2487
+ void considersAnnotatedBeanNamesFromRepository() {
- var startup = Mockito.spy(ApplicationStartup.DEFAULT);
+ var environment = new StandardEnvironment();
+ var context = new GenericApplicationContext();
+
+ RepositoryConfigurationSource configSource = new AnnotationRepositoryConfigurationSource(
+ AnnotationMetadata.introspect(AnnotatedDerivedBeanNamesConfig.class), EnableRepositories.class, context,
+ environment, context.getDefaultListableBeanFactory(), new AnnotationBeanNameGenerator());
+
+ var delegate = new RepositoryConfigurationDelegate(configSource, context, environment);
+
+ delegate.registerRepositoriesIn(context, extension);
+
+ assertThat(context.getBeanFactory().getBeanDefinition("fooRepository")).isNotNull();
+ assertThat(context.getBeanFactory().getBeanDefinition("fooRepositoryImpl")).isNotNull();
+ }
+
+ @Test // GH-2487
+ void considersAnnotatedBeanNamesFromAtComponent() {
var environment = new StandardEnvironment();
var context = new GenericApplicationContext();
- context.setApplicationStartup(startup);
RepositoryConfigurationSource configSource = new AnnotationRepositoryConfigurationSource(
- new StandardAnnotationMetadata(TestConfig.class, true), EnableRepositories.class, context, environment,
- context.getDefaultListableBeanFactory());
+ AnnotationMetadata.introspect(AnnotatedBeanNamesConfig.class), EnableRepositories.class, context, environment,
+ context.getDefaultListableBeanFactory(), new AnnotationBeanNameGenerator());
var delegate = new RepositoryConfigurationDelegate(configSource, context, environment);
delegate.registerRepositoriesIn(context, extension);
- Mockito.verify(startup).start("spring.data.repository.scanning");
+ assertThat(context.getBeanFactory().getBeanDefinition("myAnnotatedRepository")).isNotNull();
+ assertThat(context.getBeanFactory().getBeanDefinition("anotherBeanName")).isNotNull();
+ assertThat(context.getBeanFactory().getBeanDefinition("fragment")).isNotNull();
+ assertThat(context.getBeanFactory().getBeanDefinition("fragmentFragment")).isNotNull();
+ }
+
+ @Test // GH-2487
+ void skipsRegistrationOnAlreadyRegisteredBeansUsingAtComponentNames() {
+
+ var environment = new StandardEnvironment();
+ var context = new GenericApplicationContext();
+ context.setAllowBeanDefinitionOverriding(false);
+ context.registerBean("fragment", MyFragmentImpl.class);
+ context.registerBean("anotherBeanName", MyAnnotatedRepositoryImpl.class);
+
+ RepositoryConfigurationSource configSource = new AnnotationRepositoryConfigurationSource(
+ AnnotationMetadata.introspect(AnnotatedBeanNamesConfig.class), EnableRepositories.class, context, environment,
+ context.getDefaultListableBeanFactory(), new AnnotationBeanNameGenerator());
+
+ var delegate = new RepositoryConfigurationDelegate(configSource, context, environment);
+
+ delegate.registerRepositoriesIn(context, extension);
+
+ assertThat(context.getBeanFactory().getBeanDefinition("myAnnotatedRepository")).isNotNull();
+ assertThat(context.getBeanFactory().getBeanDefinition("anotherBeanName")).isNotNull();
+ assertThat(context.getBeanFactory().getBeanDefinition("fragment")).isNotNull();
+ assertThat(context.getBeanFactory().getBeanDefinition("fragmentFragment")).isNotNull();
+ }
+
+ private static ListableBeanFactory assertLazyRepositoryBeanSetup(Class> configClass) {
+
+ var context = new AnnotationConfigApplicationContext(configClass);
+
+ assertThat(context.getDefaultListableBeanFactory().getAutowireCandidateResolver())
+ .isInstanceOf(LazyRepositoryInjectionPointResolver.class);
+
+ var client = context.getBean(AddressRepositoryClient.class);
+ var repository = client.getRepository();
+
+ assertThat(Advised.class.isInstance(repository)).isTrue();
+
+ var targetSource = Advised.class.cast(repository).getTargetSource();
+ assertThat(targetSource).isNotNull();
+
+ return context.getDefaultListableBeanFactory();
}
@EnableRepositories(basePackageClasses = ProductRepository.class)
@@ -140,4 +226,28 @@ static class LazyConfig {}
includeFilters = @Filter(type = FilterType.ASSIGNABLE_TYPE, classes = AddressRepository.class),
bootstrapMode = BootstrapMode.DEFERRED)
static class DeferredConfig {}
+
+ @EnableRepositories(basePackageClasses = MyOtherRepository.class,
+ includeFilters = @Filter(type = FilterType.ASSIGNABLE_TYPE, classes = MyOtherRepository.class),
+ excludeFilters = @Filter(type = FilterType.ASSIGNABLE_TYPE, classes = MyOtherRepositoryImpl.class))
+ static class DefaultBeanNamesConfig {}
+
+ @EnableRepositories(basePackageClasses = MyStereotypeRepository.class,
+ includeFilters = @Filter(type = FilterType.ASSIGNABLE_TYPE, classes = MyStereotypeRepository.class))
+ static class AnnotatedDerivedBeanNamesConfig {}
+
+ @EnableRepositories(basePackageClasses = MyAnnotatedRepository.class)
+ static class AnnotatedBeanNamesConfig {}
+
+ static class DummyConfigurationExtension extends RepositoryConfigurationExtensionSupport {
+
+ public String getRepositoryFactoryBeanClassName() {
+ return DummyRepositoryFactoryBean.class.getName();
+ }
+
+ @Override
+ protected String getModulePrefix() {
+ return "commons";
+ }
+ }
}
diff --git a/src/test/java/org/springframework/data/repository/config/annotated/MyAnnotatedRepository.java b/src/test/java/org/springframework/data/repository/config/annotated/MyAnnotatedRepository.java
new file mode 100644
index 0000000000..973743628c
--- /dev/null
+++ b/src/test/java/org/springframework/data/repository/config/annotated/MyAnnotatedRepository.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2022 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
+ *
+ * https://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.annotated;
+
+import org.springframework.data.mapping.Person;
+import org.springframework.data.repository.CrudRepository;
+
+/**
+ * @author Mark Paluch
+ */
+public interface MyAnnotatedRepository extends CrudRepository, MyFragment {}
diff --git a/src/test/java/org/springframework/data/repository/config/annotated/MyAnnotatedRepositoryImpl.java b/src/test/java/org/springframework/data/repository/config/annotated/MyAnnotatedRepositoryImpl.java
new file mode 100644
index 0000000000..d424ecc3f8
--- /dev/null
+++ b/src/test/java/org/springframework/data/repository/config/annotated/MyAnnotatedRepositoryImpl.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2022 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
+ *
+ * https://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.annotated;
+
+import org.springframework.stereotype.Component;
+
+/**
+ * @author Mark Paluch
+ */
+@Component("anotherBeanName")
+public class MyAnnotatedRepositoryImpl {}
diff --git a/src/test/java/org/springframework/data/repository/config/annotated/MyFragment.java b/src/test/java/org/springframework/data/repository/config/annotated/MyFragment.java
new file mode 100644
index 0000000000..87be1eccc3
--- /dev/null
+++ b/src/test/java/org/springframework/data/repository/config/annotated/MyFragment.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2022 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
+ *
+ * https://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.annotated;
+
+/**
+ * @author Mark Paluch
+ */
+interface MyFragment {}
diff --git a/src/test/java/org/springframework/data/repository/config/annotated/MyFragmentImpl.java b/src/test/java/org/springframework/data/repository/config/annotated/MyFragmentImpl.java
new file mode 100644
index 0000000000..2493dd9e33
--- /dev/null
+++ b/src/test/java/org/springframework/data/repository/config/annotated/MyFragmentImpl.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2022 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
+ *
+ * https://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.annotated;
+
+import org.springframework.stereotype.Component;
+
+/**
+ * @author Mark Paluch
+ */
+@Component("fragment")
+public class MyFragmentImpl implements MyFragment {}
diff --git a/src/test/java/org/springframework/data/repository/config/stereotype/MyStereotypeRepository.java b/src/test/java/org/springframework/data/repository/config/stereotype/MyStereotypeRepository.java
new file mode 100644
index 0000000000..d5fb567711
--- /dev/null
+++ b/src/test/java/org/springframework/data/repository/config/stereotype/MyStereotypeRepository.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2022 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
+ *
+ * https://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.stereotype;
+
+import org.springframework.data.mapping.Person;
+import org.springframework.data.repository.CrudRepository;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author Mark Paluch
+ */
+@Component("fooRepository")
+public interface MyStereotypeRepository extends CrudRepository {}
diff --git a/src/test/java/org/springframework/data/repository/config/stereotype/MyStereotypeRepositoryImpl.java b/src/test/java/org/springframework/data/repository/config/stereotype/MyStereotypeRepositoryImpl.java
new file mode 100644
index 0000000000..9e5321fdbf
--- /dev/null
+++ b/src/test/java/org/springframework/data/repository/config/stereotype/MyStereotypeRepositoryImpl.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2022 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
+ *
+ * https://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.stereotype;
+
+import org.springframework.stereotype.Component;
+
+/**
+ * @author Mark Paluch
+ */
+@Component("fooRepositoryImpl")
+public class MyStereotypeRepositoryImpl {}