Skip to content

Commit 88011e6

Browse files
committed
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: spring-projectsGH-3074.
1 parent dd081d4 commit 88011e6

File tree

2 files changed

+114
-10
lines changed

2 files changed

+114
-10
lines changed

src/main/java/org/springframework/data/repository/config/RepositoryConfigurationDelegate.java

+19-10
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
import org.springframework.core.metrics.StartupStep;
5050
import org.springframework.data.repository.core.RepositoryMetadata;
5151
import org.springframework.data.repository.core.support.AbstractRepositoryMetadata;
52+
import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
5253
import org.springframework.data.repository.core.support.RepositoryFactorySupport;
5354
import org.springframework.data.util.ReflectionUtils;
5455
import org.springframework.lang.Nullable;
@@ -339,22 +340,30 @@ private ResolvableType getRepositoryInterface(RepositoryConfiguration<?> configu
339340
return null;
340341
}
341342

342-
TypeVariable<?>[] variables = factoryBean.getTypeParameters();
343-
int numberOfGenerics = variables.length;
344343
RepositoryMetadata metadata = AbstractRepositoryMetadata.getMetadata(repositoryInterface);
344+
List<Class<?>> types = List.of(repositoryInterface, metadata.getDomainType(), metadata.getIdType());
345345

346-
ResolvableType[] generics = new ResolvableType[numberOfGenerics];
347-
generics[0] = ResolvableType.forClass(repositoryInterface);
348-
generics[1] = ResolvableType.forClass(metadata.getDomainType());
349-
generics[2] = ResolvableType.forClass(metadata.getIdType());
346+
ResolvableType[] declaredGenerics = ResolvableType.forClass(factoryBean).getGenerics();
347+
ResolvableType[] parentGenerics = ResolvableType.forClass(RepositoryFactoryBeanSupport.class, factoryBean)
348+
.getGenerics();
349+
List<ResolvableType> resolvedGenerics = new ArrayList<ResolvableType>(factoryBean.getTypeParameters().length);
350350

351-
if (numberOfGenerics > 3) {
352-
for (int i = 3; i < numberOfGenerics; i++) {
353-
generics[i] = ResolvableType.forType(variables[0]);
351+
for (int i = 0; i < parentGenerics.length; i++) {
352+
353+
ResolvableType parameter = parentGenerics[i];
354+
355+
if (parameter.getType() instanceof TypeVariable<?>) {
356+
resolvedGenerics.add(i < types.size() ? ResolvableType.forClass(types.get(i)) : parameter);
357+
}
358+
}
359+
360+
if (resolvedGenerics.size() < declaredGenerics.length) {
361+
for (int j = parentGenerics.length; j < declaredGenerics.length; j++) {
362+
resolvedGenerics.add(declaredGenerics[j]);
354363
}
355364
}
356365

357-
return ResolvableType.forClassWithGenerics(factoryBean, generics);
366+
return ResolvableType.forClassWithGenerics(factoryBean, resolvedGenerics.toArray(ResolvableType[]::new));
358367
}
359368

360369
/**

src/test/java/org/springframework/data/repository/config/RepositoryConfigurationDelegateUnitTests.java

+95
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@
1717

1818
import static org.assertj.core.api.Assertions.*;
1919

20+
import java.lang.reflect.TypeVariable;
21+
import java.util.List;
22+
import java.util.Optional;
23+
import java.util.UUID;
24+
2025
import org.junit.jupiter.api.Test;
2126
import org.junit.jupiter.api.extension.ExtendWith;
2227
import org.mockito.Mockito;
@@ -26,24 +31,30 @@
2631
import org.springframework.aop.framework.Advised;
2732
import org.springframework.aot.hint.RuntimeHints;
2833
import org.springframework.beans.factory.ListableBeanFactory;
34+
import org.springframework.beans.factory.config.BeanDefinition;
35+
import org.springframework.beans.factory.parsing.BeanComponentDefinition;
2936
import org.springframework.beans.factory.support.RootBeanDefinition;
3037
import org.springframework.context.annotation.AnnotationBeanNameGenerator;
3138
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
3239
import org.springframework.context.annotation.ComponentScan;
3340
import org.springframework.context.annotation.ComponentScan.Filter;
3441
import org.springframework.context.annotation.FilterType;
3542
import org.springframework.context.support.GenericApplicationContext;
43+
import org.springframework.core.ResolvableType;
3644
import org.springframework.core.env.StandardEnvironment;
3745
import org.springframework.core.metrics.ApplicationStartup;
3846
import org.springframework.core.type.AnnotationMetadata;
3947
import org.springframework.core.type.StandardAnnotationMetadata;
48+
import org.springframework.data.mapping.Person;
49+
import org.springframework.data.repository.Repository;
4050
import org.springframework.data.repository.config.RepositoryConfigurationDelegate.LazyRepositoryInjectionPointResolver;
4151
import org.springframework.data.repository.config.annotated.MyAnnotatedRepository;
4252
import org.springframework.data.repository.config.annotated.MyAnnotatedRepositoryImpl;
4353
import org.springframework.data.repository.config.annotated.MyFragmentImpl;
4454
import org.springframework.data.repository.config.excluded.MyOtherRepositoryImpl;
4555
import org.springframework.data.repository.config.stereotype.MyStereotypeRepository;
4656
import org.springframework.data.repository.core.support.DummyRepositoryFactoryBean;
57+
import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
4758
import org.springframework.data.repository.sample.AddressRepository;
4859
import org.springframework.data.repository.sample.AddressRepositoryClient;
4960
import org.springframework.data.repository.sample.ProductRepository;
@@ -223,6 +234,38 @@ void registersAotPostProcessorForDifferentConfigurations() {
223234
assertThat(context.getBeanNamesForType(RepositoryRegistrationAotProcessor.class)).hasSize(2);
224235
}
225236

237+
@Test // GH-3074
238+
void registersGenericsForIdConstrainingRepositoryFactoryBean() {
239+
240+
ResolvableType it = registerBeanDefinition(IdConstrainingRepositoryFactoryBean.class);
241+
242+
assertThat(it.getGenerics()).hasSize(2);
243+
assertThat(it.getGeneric(0).resolve()).isEqualTo(MyAnnotatedRepository.class);
244+
assertThat(it.getGeneric(1).resolve()).isEqualTo(Person.class);
245+
}
246+
247+
@Test // GH-3074
248+
void registersGenericsForDomainTypeConstrainingRepositoryFactoryBean() {
249+
250+
ResolvableType it = registerBeanDefinition(DomainTypeConstrainingRepositoryFactoryBean.class);
251+
252+
assertThat(it.getGenerics()).hasSize(2);
253+
assertThat(it.getGeneric(0).resolve()).isEqualTo(MyAnnotatedRepository.class);
254+
assertThat(it.getGeneric(1).resolve()).isEqualTo(String.class);
255+
}
256+
257+
@Test // GH-3074
258+
void registersGenericsForAdditionalGenericsRepositoryFactoryBean() {
259+
260+
ResolvableType it = registerBeanDefinition(AdditionalGenericsRepositoryFactoryBean.class);
261+
262+
assertThat(it.getGenerics()).hasSize(4);
263+
assertThat(it.getGeneric(0).resolve()).isEqualTo(MyAnnotatedRepository.class);
264+
assertThat(it.getGeneric(1).resolve()).isEqualTo(Person.class);
265+
assertThat(it.getGeneric(2).resolve()).isEqualTo(String.class);
266+
assertThat(it.getGeneric(3).getType()).isInstanceOf(TypeVariable.class);
267+
}
268+
226269
private static ListableBeanFactory assertLazyRepositoryBeanSetup(Class<?> configClass) {
227270

228271
var context = new AnnotationConfigApplicationContext(configClass);
@@ -279,4 +322,56 @@ protected String getModulePrefix() {
279322
return "commons";
280323
}
281324
}
325+
326+
private ResolvableType registerBeanDefinition(Class<?> repositoryFactoryType) {
327+
328+
AnnotationMetadata metadata = AnnotationMetadata.introspect(AnnotatedBeanNamesConfig.class);
329+
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
330+
331+
RepositoryConfigurationSource source = new AnnotationRepositoryConfigurationSource(metadata,
332+
EnableRepositories.class, context, context.getEnvironment(),
333+
context.getDefaultListableBeanFactory(), new AnnotationBeanNameGenerator()) {
334+
335+
@Override
336+
public Optional<String> getRepositoryFactoryBeanClassName() {
337+
return Optional.of(repositoryFactoryType.getName());
338+
}
339+
};
340+
341+
RepositoryConfigurationDelegate delegate = new RepositoryConfigurationDelegate(source, context,
342+
context.getEnvironment());
343+
344+
List<BeanComponentDefinition> repositories = delegate.registerRepositoriesIn(context, extension);
345+
346+
assertThat(repositories).hasSize(1).element(0)
347+
.extracting(BeanComponentDefinition::getBeanDefinition)
348+
.extracting(BeanDefinition::getResolvableType)
349+
.isNotNull();
350+
351+
return repositories.get(0).getBeanDefinition().getResolvableType();
352+
}
353+
354+
static abstract class IdConstrainingRepositoryFactoryBean<T extends Repository<S, UUID>, S>
355+
extends RepositoryFactoryBeanSupport<T, S, UUID> {
356+
357+
protected IdConstrainingRepositoryFactoryBean(Class<? extends T> repositoryInterface) {
358+
super(repositoryInterface);
359+
}
360+
}
361+
362+
static abstract class DomainTypeConstrainingRepositoryFactoryBean<T extends Repository<Person, ID>, ID>
363+
extends RepositoryFactoryBeanSupport<T, Person, ID> {
364+
365+
protected DomainTypeConstrainingRepositoryFactoryBean(Class<? extends T> repositoryInterface) {
366+
super(repositoryInterface);
367+
}
368+
}
369+
370+
static abstract class AdditionalGenericsRepositoryFactoryBean<T extends Repository<S, ID>, S, ID, R>
371+
extends RepositoryFactoryBeanSupport<T, S, ID> {
372+
373+
protected AdditionalGenericsRepositoryFactoryBean(Class<? extends T> repositoryInterface) {
374+
super(repositoryInterface);
375+
}
376+
}
282377
}

0 commit comments

Comments
 (0)