From d22bb69a46b326548625a0cc2772e9a2130ed65e Mon Sep 17 00:00:00 2001 From: Oliver Drotbohm Date: Tue, 9 Apr 2024 19:20:28 +0200 Subject: [PATCH] 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. --- .../RepositoryConfigurationDelegate.java | 23 ++++--- ...ositoryConfigurationDelegateUnitTests.java | 63 ++++++++++++++++--- 2 files changed, 71 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationDelegate.java b/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationDelegate.java index 6cdda4a04b..140dde8bb7 100644 --- a/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationDelegate.java +++ b/src/main/java/org/springframework/data/repository/config/RepositoryConfigurationDelegate.java @@ -343,19 +343,28 @@ private ResolvableType getRepositoryInterface(RepositoryConfiguration configu RepositoryMetadata metadata = AbstractRepositoryMetadata.getMetadata(repositoryInterface); List> 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 resolvedGenerics = new ArrayList(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); + } + } + + if (resolvedGenerics.size() < declaredGenerics.length) { + for (int j = parentGenerics.length; j < declaredGenerics.length; j++) { + resolvedGenerics.add(declaredGenerics[j]); } } - return ResolvableType.forClassWithGenerics(factoryBean, factoryGenerics); + return ResolvableType.forClassWithGenerics(factoryBean, resolvedGenerics.toArray(ResolvableType[]::new)); } /** 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 5f7907fbc2..71a13568d7 100644 --- a/src/test/java/org/springframework/data/repository/config/RepositoryConfigurationDelegateUnitTests.java +++ b/src/test/java/org/springframework/data/repository/config/RepositoryConfigurationDelegateUnitTests.java @@ -17,6 +17,7 @@ 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.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 @@ void registersAotPostProcessorForDifferentConfigurations() { 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 @@ protected String getModulePrefix() { } } - @Test // GH-3074 - void registersGenericsForConstrainingRepositoryFactoryBean() { + private ResolvableType registerBeanDefinition(Class repositoryFactoryType) { AnnotationMetadata metadata = AnnotationMetadata.introspect(AnnotatedBeanNamesConfig.class); AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); @@ -301,7 +334,7 @@ void registersGenericsForConstrainingRepositoryFactoryBean() { @Override public Optional getRepositoryFactoryBeanClassName() { - return Optional.of(IdConstrainingRepositoryFactoryBean.class.getName()); + return Optional.of(repositoryFactoryType.getName()); } }; @@ -313,11 +346,9 @@ public Optional getRepositoryFactoryBeanClassName() { 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, S> @@ -327,4 +358,20 @@ protected IdConstrainingRepositoryFactoryBean(Class repositoryInter super(repositoryInterface); } } + + static abstract class DomainTypeConstrainingRepositoryFactoryBean, ID> + extends RepositoryFactoryBeanSupport { + + protected DomainTypeConstrainingRepositoryFactoryBean(Class repositoryInterface) { + super(repositoryInterface); + } + } + + static abstract class AdditionalGenericsRepositoryFactoryBean, S, ID, R> + extends RepositoryFactoryBeanSupport { + + protected AdditionalGenericsRepositoryFactoryBean(Class repositoryInterface) { + super(repositoryInterface); + } + } }