Add support for BeanNameGenerator configuration in Enable…Repositories.

We now accept a dedicated BeanNameGenerator in our Enable…Repositories annotations to override the importBeanNameGenerator.

Closes: #3082
Original Pull Request: #3083
This commit is contained in:
Mark Paluch
2024-04-18 16:03:10 +02:00
committed by Christoph Strobl
parent a115ee6a44
commit a37a700b59
4 changed files with 79 additions and 4 deletions

View File

@@ -24,6 +24,7 @@ import java.util.Set;
import java.util.function.Function;
import java.util.stream.Stream;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.context.annotation.AnnotationBeanNameGenerator;
@@ -64,6 +65,7 @@ public class AnnotationRepositoryConfigurationSource extends RepositoryConfigura
private static final String REPOSITORY_BASE_CLASS = "repositoryBaseClass";
private static final String CONSIDER_NESTED_REPOSITORIES = "considerNestedRepositories";
private static final String BOOTSTRAP_MODE = "bootstrapMode";
private static final String BEAN_NAME_GENERATOR = "nameGenerator";
private final AnnotationMetadata configMetadata;
private final AnnotationMetadata enableAnnotationMetadata;
@@ -97,14 +99,15 @@ public class AnnotationRepositoryConfigurationSource extends RepositoryConfigura
* @param resourceLoader must not be {@literal null}.
* @param environment must not be {@literal null}.
* @param registry must not be {@literal null}.
* @param generator can be {@literal null}.
* @param importBeanNameGenerator can be {@literal null}.
*/
public AnnotationRepositoryConfigurationSource(AnnotationMetadata metadata, Class<? extends Annotation> annotation,
ResourceLoader resourceLoader, Environment environment, BeanDefinitionRegistry registry,
@Nullable BeanNameGenerator generator) {
@Nullable BeanNameGenerator importBeanNameGenerator) {
super(environment, ConfigurationUtils.getRequiredClassLoader(resourceLoader), registry,
defaultBeanNameGenerator(generator));
configuredOrDefaultBeanNameGenerator(metadata, annotation,
ConfigurationUtils.getRequiredClassLoader(resourceLoader), importBeanNameGenerator));
Assert.notNull(metadata, "Metadata must not be null");
Assert.notNull(annotation, "Annotation must not be null");
@@ -305,6 +308,24 @@ public class AnnotationRepositoryConfigurationSource extends RepositoryConfigura
.anyMatch(it -> attributes.getAnnotationArray(it).length > 0);
}
private static BeanNameGenerator configuredOrDefaultBeanNameGenerator(AnnotationMetadata metadata,
Class<? extends Annotation> annotation, ClassLoader beanClassLoader,
@Nullable BeanNameGenerator importBeanNameGenerator) {
Map<String, Object> annotationAttributes = metadata.getAnnotationAttributes(annotation.getName());
if (annotationAttributes != null) {
BeanNameGenerator beanNameGenerator = getBeanNameGenerator(annotationAttributes, beanClassLoader);
if (beanNameGenerator != null) {
return beanNameGenerator;
}
}
return defaultBeanNameGenerator(importBeanNameGenerator);
}
/**
* Returns the {@link BeanNameGenerator} to use falling back to an {@link AnnotationBeanNameGenerator} if either the
* given generator is {@literal null} or it's the one locally declared in {@link ConfigurationClassPostProcessor}'s
@@ -321,4 +342,37 @@ public class AnnotationRepositoryConfigurationSource extends RepositoryConfigura
? new AnnotationBeanNameGenerator() //
: generator;
}
/**
* Obtain a configured {@link BeanNameGenerator}.
*
* @param beanClassLoader a class loader to load the configured {@link BeanNameGenerator} class in case it was
* configured as String instead of a Class instance.
* @return the bean name generator.
*/
@Nullable
@SuppressWarnings("unchecked")
private static BeanNameGenerator getBeanNameGenerator(Map<String, Object> annotationAttributes,
ClassLoader beanClassLoader) {
Object configuredBeanNameGenerator = annotationAttributes.get(BEAN_NAME_GENERATOR);
if (configuredBeanNameGenerator == null) {
return null;
}
if (configuredBeanNameGenerator instanceof String cbng) {
try {
configuredBeanNameGenerator = ClassUtils.forName(cbng, beanClassLoader);
} catch (Exception o_O) {
throw new RuntimeException(o_O);
}
}
if (configuredBeanNameGenerator != BeanNameGenerator.class) {
return BeanUtils.instantiateClass((Class<? extends BeanNameGenerator>) configuredBeanNameGenerator);
}
return null;
}
}

View File

@@ -174,7 +174,7 @@ public interface RepositoryConfigurationSource {
BootstrapMode getBootstrapMode();
/**
* Returns a human readable description of the repository configuration source for error reporting purposes.
* Returns a human-readable description of the repository configuration source for error reporting purposes.
*
* @return can be {@literal null}.
* @since 2.3

View File

@@ -24,7 +24,9 @@ import java.lang.annotation.RetentionPolicy;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.FullyQualifiedAnnotationBeanNameGenerator;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;
import org.springframework.core.env.StandardEnvironment;
@@ -33,6 +35,8 @@ import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.StandardAnnotationMetadata;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.config.basepackage.repo.PersonRepository;
import org.springframework.data.repository.core.support.DummyRepositoryFactory;
/**
* Unit tests for {@link AnnotationRepositoryConfigurationSource}.
@@ -165,6 +169,17 @@ class AnnotationRepositoryConfigurationSourceUnitTests {
.isThrownBy(() -> source.getRequiredAttribute("namedQueriesLocation", String.class));
}
@Test // GH-3082
void considerBeanNameGenerator() {
RootBeanDefinition bd = new RootBeanDefinition(DummyRepositoryFactory.class);
bd.getConstructorArgumentValues().addGenericArgumentValue(PersonRepository.class);
assertThat(getConfigSource(ConfigurationWithBeanNameGenerator.class).generateBeanName(bd))
.isEqualTo("org.springframework.data.repository.config.basepackage.repo.PersonRepository");
assertThat(getConfigSource(DefaultConfiguration.class).generateBeanName(bd)).isEqualTo("personRepository");
}
private AnnotationRepositoryConfigurationSource getConfigSource(Class<?> type) {
AnnotationMetadata metadata = new StandardAnnotationMetadata(type, true);
@@ -186,6 +201,9 @@ class AnnotationRepositoryConfigurationSourceUnitTests {
@EnableRepositories(excludeFilters = { @Filter(Primary.class) })
static class ConfigurationWithExplicitFilter {}
@EnableRepositories(nameGenerator = FullyQualifiedAnnotationBeanNameGenerator.class)
static class ConfigurationWithBeanNameGenerator {}
@Retention(RetentionPolicy.RUNTIME)
@interface SampleAnnotation {

View File

@@ -19,6 +19,7 @@ import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import org.springframework.beans.factory.support.BeanNameGenerator;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Import;
import org.springframework.data.repository.PagingAndSortingRepository;
@@ -43,6 +44,8 @@ public @interface EnableRepositories {
Class<?> repositoryBaseClass() default PagingAndSortingRepository.class;
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
String namedQueriesLocation() default "";
String repositoryImplementationPostfix() default "Impl";