Consider component names for custom implementation and fragment bean registration.
We now consider the the actual repository & fragment bean name when checking for existing bean definitions of default custom implementation beans. Previously, we used the repository interface name without considering the repository bean name. Closes #2487. Original Pull Request: #2488
This commit is contained in:
committed by
Christoph Strobl
parent
103d41f7f4
commit
70cda7949d
@@ -18,6 +18,7 @@ package org.springframework.data.repository.config;
|
||||
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 class CustomRepositoryImplementationDetector {
|
||||
.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<AbstractBeanDefinition> selectImplementationCandidate(
|
||||
ImplementationLookupConfiguration lookup, Set<BeanDefinition> definitions,
|
||||
Supplier<Optional<BeanDefinition>> 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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -22,6 +22,7 @@ import org.springframework.core.type.classreading.MetadataReaderFactory;
|
||||
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<T extends RepositoryConfigurationSou
|
||||
private final T configurationSource;
|
||||
private final BeanDefinition definition;
|
||||
private final RepositoryConfigurationExtension extension;
|
||||
private final Lazy<String> beanName;
|
||||
|
||||
public DefaultRepositoryConfiguration(T configurationSource, BeanDefinition definition,
|
||||
RepositoryConfigurationExtension extension) {
|
||||
@@ -52,6 +54,7 @@ public class DefaultRepositoryConfiguration<T extends RepositoryConfigurationSou
|
||||
this.configurationSource = configurationSource;
|
||||
this.definition = definition;
|
||||
this.extension = extension;
|
||||
this.beanName = Lazy.of(() -> configurationSource.generateBeanName(definition));
|
||||
}
|
||||
|
||||
public String getBeanId() {
|
||||
@@ -90,8 +93,7 @@ public class DefaultRepositoryConfiguration<T extends RepositoryConfigurationSou
|
||||
}
|
||||
|
||||
public String getImplementationBeanName() {
|
||||
return configurationSource.generateBeanName(definition)
|
||||
+ configurationSource.getRepositoryImplementationPostfix().orElse("Impl");
|
||||
return beanName.get() + configurationSource.getRepositoryImplementationPostfix().orElse("Impl");
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -117,6 +119,11 @@ public class DefaultRepositoryConfiguration<T extends RepositoryConfigurationSou
|
||||
.orElseGet(extension::getRepositoryFactoryBeanClassName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRepositoryBeanName() {
|
||||
return beanName.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isLazyInit() {
|
||||
return definition.isLazyInit() || !configurationSource.getBootstrapMode().equals(BootstrapMode.DEFAULT);
|
||||
|
||||
@@ -90,7 +90,8 @@ public interface ImplementationDetectionConfiguration {
|
||||
|
||||
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 @@ public interface ImplementationDetectionConfiguration {
|
||||
|
||||
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<String> getBasePackages() {
|
||||
|
||||
@@ -147,7 +147,7 @@ class RepositoryBeanDefinitionBuilder {
|
||||
|
||||
List<RepositoryFragmentConfiguration> 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 @@ class RepositoryBeanDefinitionBuilder {
|
||||
|
||||
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<AbstractBeanDefinition> 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());
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
it.setSource(configuration.getSource());
|
||||
registry.registerBeanDefinition(beanName, it);
|
||||
|
||||
return beanName;
|
||||
return scannedBeanName;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -232,19 +248,20 @@ class RepositoryBeanDefinitionBuilder {
|
||||
.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<RepositoryFragmentConfiguration> detectRepositoryFragmentConfiguration(String fragmentInterface,
|
||||
ImplementationDetectionConfiguration config) {
|
||||
ImplementationDetectionConfiguration config, RepositoryConfiguration<?> configuration) {
|
||||
|
||||
ImplementationLookupConfiguration lookup = config.forFragment(fragmentInterface);
|
||||
Optional<AbstractBeanDefinition> 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 @@ class RepositoryBeanDefinitionBuilder {
|
||||
|
||||
// Already a bean configured?
|
||||
if (registry.containsBeanDefinition(beanName)) {
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(String.format("Repository fragment implementation already registered: %s", beanName));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(String.format("Registering repository fragment implementation: %s %s", beanName,
|
||||
fragmentConfiguration.getClassName()));
|
||||
}
|
||||
|
||||
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 @@ class RepositoryBeanDefinitionBuilder {
|
||||
|
||||
// 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,
|
||||
|
||||
@@ -84,6 +84,22 @@ public interface RepositoryConfiguration<T extends RepositoryConfigurationSource
|
||||
*/
|
||||
String getRepositoryFactoryBeanClassName();
|
||||
|
||||
/**
|
||||
* Returns the custom implementation bean name to be used.
|
||||
*
|
||||
* @return
|
||||
* @since 3.0
|
||||
*/
|
||||
String getImplementationBeanName();
|
||||
|
||||
/**
|
||||
* Returns the bean name of the repository to be used.
|
||||
*
|
||||
* @return
|
||||
* @since 3.0
|
||||
*/
|
||||
String getRepositoryBeanName();
|
||||
|
||||
/**
|
||||
* Returns the source of the {@link RepositoryConfiguration}.
|
||||
*
|
||||
|
||||
@@ -75,6 +75,16 @@ class RepositoryConfigurationAdapter<T extends RepositoryConfigurationSource>
|
||||
return repositoryConfiguration.getRepositoryFactoryBeanClassName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getImplementationBeanName() {
|
||||
return repositoryConfiguration.getImplementationBeanName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRepositoryBeanName() {
|
||||
return repositoryConfiguration.getRepositoryBeanName();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Object getSource() {
|
||||
|
||||
@@ -36,6 +36,7 @@ public final class RepositoryFragmentConfiguration {
|
||||
private final String interfaceName;
|
||||
private final String className;
|
||||
private final Optional<AbstractBeanDefinition> 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 final class RepositoryFragmentConfiguration {
|
||||
this.interfaceName = interfaceName;
|
||||
this.className = ConfigurationUtils.getRequiredBeanClassName(beanDefinition);
|
||||
this.beanDefinition = Optional.of(beanDefinition);
|
||||
this.beanName = generateBeanName();
|
||||
}
|
||||
|
||||
public RepositoryFragmentConfiguration(String interfaceName, String className,
|
||||
Optional<AbstractBeanDefinition> beanDefinition) {
|
||||
RepositoryFragmentConfiguration(String interfaceName, AbstractBeanDefinition beanDefinition, String beanName) {
|
||||
this(interfaceName, ConfigurationUtils.getRequiredBeanClassName(beanDefinition), Optional.of(beanDefinition),
|
||||
beanName);
|
||||
}
|
||||
|
||||
private RepositoryFragmentConfiguration(String interfaceName, String className,
|
||||
Optional<AbstractBeanDefinition> 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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,6 +19,9 @@ import static org.assertj.core.api.Assertions.*;
|
||||
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 @@ import org.springframework.mock.env.MockEnvironment;
|
||||
* 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 @@ class CustomRepositoryImplementationDetectorUnitTests {
|
||||
@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 @@ class CustomRepositoryImplementationDetectorUnitTests {
|
||||
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 @@ class CustomRepositoryImplementationDetectorUnitTests {
|
||||
});
|
||||
}
|
||||
|
||||
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()));
|
||||
|
||||
|
||||
@@ -18,8 +18,14 @@ package org.springframework.data.repository.config;
|
||||
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,12 +34,18 @@ import org.junit.jupiter.api.Test;
|
||||
*/
|
||||
class DefaultImplementationLookupConfigurationUnitTests {
|
||||
|
||||
ImplementationDetectionConfiguration 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() {
|
||||
|
||||
var idcMock = mock(ImplementationDetectionConfiguration.class);
|
||||
when(idcMock.getImplementationPostfix()).thenReturn("Impl");
|
||||
|
||||
assertThat(getImplementationBeanName(idcMock, "com.acme.UDPRepository")).isEqualTo("UDPRepositoryImpl");
|
||||
assertThat(getImplementationBeanName(idcMock, "com.acme.UdpRepository")).isEqualTo("udpRepositoryImpl");
|
||||
}
|
||||
@@ -41,19 +53,20 @@ class DefaultImplementationLookupConfigurationUnitTests {
|
||||
@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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ class RepositoryComponentProviderUnitTests {
|
||||
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 @@ class RepositoryComponentProviderUnitTests {
|
||||
assertThat(provider.getRegistry()).isEqualTo(registry);
|
||||
}
|
||||
|
||||
interface MyNestedRepository extends Repository<Person, Long> {}
|
||||
interface MyNestedRepositoryDefinition extends Repository<Person, Long> {}
|
||||
}
|
||||
|
||||
@@ -19,14 +19,15 @@ import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
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.annotation.FilterType;
|
||||
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 @@ import org.springframework.data.repository.sample.ProductRepository;
|
||||
* 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 @@ class RepositoryConfigurationDelegateUnitTests {
|
||||
|
||||
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,25 +95,6 @@ class RepositoryConfigurationDelegateUnitTests {
|
||||
|
||||
}
|
||||
|
||||
private static ListableBeanFactory assertLazyRepositoryBeanSetup(Class<?> configClass) {
|
||||
|
||||
var environment = new StandardEnvironment();
|
||||
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();
|
||||
}
|
||||
|
||||
@Test // DATACMNS-1832
|
||||
void writesRepositoryScanningMetrics() {
|
||||
|
||||
@@ -126,6 +115,103 @@ class RepositoryConfigurationDelegateUnitTests {
|
||||
Mockito.verify(startup).start("spring.data.repository.scanning");
|
||||
}
|
||||
|
||||
@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 // GH-2487
|
||||
void considersAnnotatedBeanNamesFromRepository() {
|
||||
|
||||
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();
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
@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)
|
||||
static class TestConfig {}
|
||||
|
||||
@@ -140,4 +226,28 @@ class RepositoryConfigurationDelegateUnitTests {
|
||||
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";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Person, String>, MyFragment {}
|
||||
@@ -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 {}
|
||||
@@ -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 {}
|
||||
@@ -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 {}
|
||||
@@ -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<Person, String> {}
|
||||
@@ -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 {}
|
||||
Reference in New Issue
Block a user