Support advanced generics redeclarations in RepositoryFactoryBeanSupport extensions.

Spring Data modules might override, and, by that, fix some of the generic type parameters exposed by RepositoryFactoryBeanSupport. We now more thoroughly walk through them to consider the ones expanded already and automatically expand the remaining ones with either the types found on the user repository interface or the unresolved type variable.

Ticket: GH-3074.
This commit is contained in:
Oliver Drotbohm
2024-04-09 19:20:28 +02:00
parent 36e60d80e0
commit d22bb69a46
2 changed files with 71 additions and 15 deletions

View File

@@ -343,19 +343,28 @@ public class RepositoryConfigurationDelegate {
RepositoryMetadata metadata = AbstractRepositoryMetadata.getMetadata(repositoryInterface);
List<Class<?>> types = List.of(repositoryInterface, metadata.getDomainType(), metadata.getIdType());
ResolvableType factoryBeanType = ResolvableType.forClass(RepositoryFactoryBeanSupport.class, factoryBean);
ResolvableType[] factoryGenerics = factoryBeanType.getGenerics();
ResolvableType[] declaredGenerics = ResolvableType.forClass(factoryBean).getGenerics();
ResolvableType[] parentGenerics = ResolvableType.forClass(RepositoryFactoryBeanSupport.class, factoryBean)
.getGenerics();
List<ResolvableType> resolvedGenerics = new ArrayList<ResolvableType>(factoryBean.getTypeParameters().length);
for (int i = 0; i < factoryGenerics.length; i++) {
ResolvableType parameter = factoryGenerics[i];
for (int i = 0; i < parentGenerics.length; i++) {
if (parameter.getType() instanceof TypeVariable<?> && i < types.size()) {
factoryGenerics[i] = ResolvableType.forClass(types.get(i));
ResolvableType parameter = parentGenerics[i];
if (parameter.getType() instanceof TypeVariable<?>) {
resolvedGenerics.add(i < types.size() ? ResolvableType.forClass(types.get(i)) : parameter);
}
}
return ResolvableType.forClassWithGenerics(factoryBean, factoryGenerics);
if (resolvedGenerics.size() < declaredGenerics.length) {
for (int j = parentGenerics.length; j < declaredGenerics.length; j++) {
resolvedGenerics.add(declaredGenerics[j]);
}
}
return ResolvableType.forClassWithGenerics(factoryBean, resolvedGenerics.toArray(ResolvableType[]::new));
}
/**

View File

@@ -17,6 +17,7 @@ package org.springframework.data.repository.config;
import static org.assertj.core.api.Assertions.*;
import java.lang.reflect.TypeVariable;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
@@ -39,6 +40,7 @@ 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.ResolvableType;
import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.metrics.ApplicationStartup;
import org.springframework.core.type.AnnotationMetadata;
@@ -232,6 +234,38 @@ class RepositoryConfigurationDelegateUnitTests {
assertThat(context.getBeanNamesForType(RepositoryRegistrationAotProcessor.class)).hasSize(2);
}
@Test // GH-3074
void registersGenericsForIdConstrainingRepositoryFactoryBean() {
ResolvableType it = registerBeanDefinition(IdConstrainingRepositoryFactoryBean.class);
assertThat(it.getGenerics()).hasSize(2);
assertThat(it.getGeneric(0).resolve()).isEqualTo(MyAnnotatedRepository.class);
assertThat(it.getGeneric(1).resolve()).isEqualTo(Person.class);
}
@Test // GH-3074
void registersGenericsForDomainTypeConstrainingRepositoryFactoryBean() {
ResolvableType it = registerBeanDefinition(DomainTypeConstrainingRepositoryFactoryBean.class);
assertThat(it.getGenerics()).hasSize(2);
assertThat(it.getGeneric(0).resolve()).isEqualTo(MyAnnotatedRepository.class);
assertThat(it.getGeneric(1).resolve()).isEqualTo(String.class);
}
@Test // GH-3074
void registersGenericsForAdditionalGenericsRepositoryFactoryBean() {
ResolvableType it = registerBeanDefinition(AdditionalGenericsRepositoryFactoryBean.class);
assertThat(it.getGenerics()).hasSize(4);
assertThat(it.getGeneric(0).resolve()).isEqualTo(MyAnnotatedRepository.class);
assertThat(it.getGeneric(1).resolve()).isEqualTo(Person.class);
assertThat(it.getGeneric(2).resolve()).isEqualTo(String.class);
assertThat(it.getGeneric(3).getType()).isInstanceOf(TypeVariable.class);
}
private static ListableBeanFactory assertLazyRepositoryBeanSetup(Class<?> configClass) {
var context = new AnnotationConfigApplicationContext(configClass);
@@ -289,8 +323,7 @@ class RepositoryConfigurationDelegateUnitTests {
}
}
@Test // GH-3074
void registersGenericsForConstrainingRepositoryFactoryBean() {
private ResolvableType registerBeanDefinition(Class<?> repositoryFactoryType) {
AnnotationMetadata metadata = AnnotationMetadata.introspect(AnnotatedBeanNamesConfig.class);
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
@@ -301,7 +334,7 @@ class RepositoryConfigurationDelegateUnitTests {
@Override
public Optional<String> getRepositoryFactoryBeanClassName() {
return Optional.of(IdConstrainingRepositoryFactoryBean.class.getName());
return Optional.of(repositoryFactoryType.getName());
}
};
@@ -313,11 +346,9 @@ class RepositoryConfigurationDelegateUnitTests {
assertThat(repositories).hasSize(1).element(0)
.extracting(BeanComponentDefinition::getBeanDefinition)
.extracting(BeanDefinition::getResolvableType)
.satisfies(it -> {
assertThat(it.getGenerics()).hasSize(2);
assertThat(it.getGeneric(0).resolve()).isEqualTo(MyAnnotatedRepository.class);
assertThat(it.getGeneric(1).resolve()).isEqualTo(Person.class);
});
.isNotNull();
return repositories.get(0).getBeanDefinition().getResolvableType();
}
static abstract class IdConstrainingRepositoryFactoryBean<T extends Repository<S, UUID>, S>
@@ -327,4 +358,20 @@ class RepositoryConfigurationDelegateUnitTests {
super(repositoryInterface);
}
}
static abstract class DomainTypeConstrainingRepositoryFactoryBean<T extends Repository<Person, ID>, ID>
extends RepositoryFactoryBeanSupport<T, Person, ID> {
protected DomainTypeConstrainingRepositoryFactoryBean(Class<? extends T> repositoryInterface) {
super(repositoryInterface);
}
}
static abstract class AdditionalGenericsRepositoryFactoryBean<T extends Repository<S, ID>, S, ID, R>
extends RepositoryFactoryBeanSupport<T, S, ID> {
protected AdditionalGenericsRepositoryFactoryBean(Class<? extends T> repositoryInterface) {
super(repositoryInterface);
}
}
}