Browse Source

Refine TypeName discovery.

Add Additional tests for arrays and back off when detecting unresolvable return type.

See: #3374
Original Pull Request: #3378
pull/3386/head
Christoph Strobl 3 months ago
parent
commit
db5c5b357d
No known key found for this signature in database
GPG Key ID: E6054036D0C37A4B
  1. 44
      src/main/java/org/springframework/data/javapoet/TypeNames.java
  2. 15
      src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryCreator.java
  3. 23
      src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryFragmentMetadata.java
  4. 3
      src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryMethodBuilder.java
  5. 7
      src/main/java/org/springframework/data/repository/aot/generate/MethodMetadata.java
  6. 4
      src/main/java/org/springframework/data/repository/aot/generate/MethodReturn.java
  7. 28
      src/test/java/example/BaseRepository.java
  8. 2
      src/test/java/example/UserRepository.java
  9. 110
      src/test/java/org/springframework/data/javapoet/TypeNamesUnitTests.java
  10. 46
      src/test/java/org/springframework/data/repository/aot/generate/AotRepositoryCreatorUnitTests.java
  11. 7
      src/test/java/org/springframework/data/repository/aot/generate/AotRepositoryMethodBuilderUnitTests.java

44
src/main/java/org/springframework/data/javapoet/TypeNames.java

@ -15,7 +15,12 @@
*/ */
package org.springframework.data.javapoet; package org.springframework.data.javapoet;
import java.util.Arrays;
import org.springframework.core.ResolvableType; import org.springframework.core.ResolvableType;
import org.springframework.javapoet.ArrayTypeName;
import org.springframework.javapoet.ClassName;
import org.springframework.javapoet.ParameterizedTypeName;
import org.springframework.javapoet.TypeName; import org.springframework.javapoet.TypeName;
import org.springframework.util.ClassUtils; import org.springframework.util.ClassUtils;
@ -28,6 +33,7 @@ import org.springframework.util.ClassUtils;
* Mainly for internal use within the framework * Mainly for internal use within the framework
* *
* @author Mark Paluch * @author Mark Paluch
* @author Christoph Strobl
* @since 4.0 * @since 4.0
*/ */
public abstract class TypeNames { public abstract class TypeNames {
@ -65,6 +71,42 @@ public abstract class TypeNames {
return TypeName.get(resolvableType.toClass()); return TypeName.get(resolvableType.toClass());
} }
/**
* Obtain a {@link TypeName} for the underlying type of the given {@link ResolvableType}. Can render a class name, a
* type signature with resolved generics or a generic type variable.
*
* @param resolvableType the resolvable type represent.
* @return the corresponding {@link TypeName}.
*/
public static TypeName resolvedTypeName(ResolvableType resolvableType) {
if (resolvableType.equals(ResolvableType.NONE)) {
return TypeName.get(Object.class);
}
if (!resolvableType.hasGenerics()) {
Class<?> resolvedType = resolvableType.toClass();
if (!resolvableType.isArray()) {
return TypeName.get(resolvedType);
}
if (resolvedType.isArray()) {
return TypeName.get(resolvedType);
}
if (resolvableType.isArray()) {
return ArrayTypeName.of(resolvedType);
}
return TypeName.get(resolvedType);
}
if (resolvableType.hasResolvableGenerics()) {
return ParameterizedTypeName.get(ClassName.get(resolvableType.toClass()),
Arrays.stream(resolvableType.getGenerics()).map(TypeNames::resolvedTypeName).toArray(TypeName[]::new));
}
return ClassName.get(resolvableType.toClass());
}
/** /**
* Obtain a {@link TypeName} for the underlying type of the given {@link ResolvableType}. Can render a class name, a * Obtain a {@link TypeName} for the underlying type of the given {@link ResolvableType}. Can render a class name, a
* type signature or a generic type variable. * type signature or a generic type variable.
@ -98,7 +140,7 @@ public abstract class TypeNames {
public static TypeName typeNameOrWrapper(ResolvableType resolvableType) { public static TypeName typeNameOrWrapper(ResolvableType resolvableType) {
return ClassUtils.isPrimitiveOrWrapper(resolvableType.toClass()) return ClassUtils.isPrimitiveOrWrapper(resolvableType.toClass())
? TypeName.get(ClassUtils.resolvePrimitiveIfNecessary(resolvableType.toClass())) ? TypeName.get(ClassUtils.resolvePrimitiveIfNecessary(resolvableType.toClass()))
: typeName(resolvableType); : resolvedTypeName(resolvableType);
} }
private TypeNames() {} private TypeNames() {}

15
src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryCreator.java

@ -27,7 +27,7 @@ import javax.lang.model.element.Modifier;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import org.springframework.core.ResolvableType;
import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.repository.core.RepositoryInformation; import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.support.RepositoryComposition; import org.springframework.data.repository.core.support.RepositoryComposition;
@ -298,6 +298,19 @@ class AotRepositoryCreator {
return; return;
} }
// TODO: should we do this even before we do something with the method to protect the modules?
if (ResolvableType.forMethodReturnType(method, repositoryInformation.getRepositoryInterface())
.hasUnresolvableGenerics()) {
if (logger.isTraceEnabled()) {
logger.trace("Skipping method [%s.%s] contribution, unresolvable generic return"
.formatted(repositoryInformation.getRepositoryInterface().getName(), method.getName()));
}
generationMetadata.addDelegateMethod(method, contributor);
return;
}
if (contributor.contributesMethodSpec() && !repositoryInformation.isReactiveRepository()) { if (contributor.contributesMethodSpec() && !repositoryInformation.isReactiveRepository()) {
generationMetadata.addRepositoryMethod(method, contributor); generationMetadata.addRepositoryMethod(method, contributor);
} else { } else {

23
src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryFragmentMetadata.java

@ -27,6 +27,7 @@ import javax.lang.model.element.Modifier;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import org.springframework.core.ResolvableType; import org.springframework.core.ResolvableType;
import org.springframework.data.javapoet.TypeNames;
import org.springframework.data.repository.core.support.RepositoryFragment; import org.springframework.data.repository.core.support.RepositoryFragment;
import org.springframework.data.repository.query.QueryMethod; import org.springframework.data.repository.query.QueryMethod;
import org.springframework.javapoet.ParameterizedTypeName; import org.springframework.javapoet.ParameterizedTypeName;
@ -147,26 +148,10 @@ class AotRepositoryFragmentMetadata {
} }
static TypeName typeNameOf(ResolvableType type) { static TypeName typeNameOf(ResolvableType type) {
return TypeNames.resolvedTypeName(type);
}
if (type.equals(ResolvableType.NONE)) { public record ConstructorArgument(String parameterName, ResolvableType parameterType, boolean bindToField,
return TypeName.get(Object.class);
}
if (!type.hasResolvableGenerics()) {
return TypeName.get(type.getType());
}
return ParameterizedTypeName.get(type.toClass(), type.resolveGenerics());
}
/**
* Constructor argument metadata.
*
* @param parameterName
* @param parameterType
* @param bindToField
*/
public record ConstructorArgument(String parameterName, ResolvableType parameterType, boolean bindToField,
AotRepositoryConstructorBuilder.ParameterOrigin parameterOrigin) { AotRepositoryConstructorBuilder.ParameterOrigin parameterOrigin) {
boolean isBoundToField() { boolean isBoundToField() {

3
src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryMethodBuilder.java

@ -24,6 +24,7 @@ import java.util.stream.Collectors;
import javax.lang.model.element.Modifier; import javax.lang.model.element.Modifier;
import org.springframework.data.javapoet.TypeNames;
import org.springframework.javapoet.CodeBlock; import org.springframework.javapoet.CodeBlock;
import org.springframework.javapoet.MethodSpec; import org.springframework.javapoet.MethodSpec;
import org.springframework.javapoet.ParameterSpec; import org.springframework.javapoet.ParameterSpec;
@ -101,7 +102,7 @@ class AotRepositoryMethodBuilder {
private MethodSpec.Builder initializeMethodBuilder() { private MethodSpec.Builder initializeMethodBuilder() {
MethodSpec.Builder builder = MethodSpec.methodBuilder(context.getMethod().getName()).addModifiers(Modifier.PUBLIC); MethodSpec.Builder builder = MethodSpec.methodBuilder(context.getMethod().getName()).addModifiers(Modifier.PUBLIC);
builder.returns(TypeName.get(context.getReturnType().getType())); builder.returns(TypeNames.resolvedTypeName(context.getTargetMethodMetadata().getReturnType()));
TypeVariable<Method>[] tvs = context.getMethod().getTypeParameters(); TypeVariable<Method>[] tvs = context.getMethod().getTypeParameters();
for (TypeVariable<Method> tv : tvs) { for (TypeVariable<Method> tv : tvs) {

7
src/main/java/org/springframework/data/repository/aot/generate/MethodMetadata.java

@ -31,6 +31,7 @@ import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.MethodParameter; import org.springframework.core.MethodParameter;
import org.springframework.core.ParameterNameDiscoverer; import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.ResolvableType; import org.springframework.core.ResolvableType;
import org.springframework.data.javapoet.TypeNames;
import org.springframework.data.repository.core.RepositoryInformation; import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.util.TypeInformation; import org.springframework.data.util.TypeInformation;
import org.springframework.javapoet.ParameterSpec; import org.springframework.javapoet.ParameterSpec;
@ -80,11 +81,11 @@ class MethodMetadata {
for (Parameter parameter : method.getParameters()) { for (Parameter parameter : method.getParameters()) {
MethodParameter methodParameter = MethodParameter.forParameter(parameter); MethodParameter methodParameter = MethodParameter.forParameter(parameter).withContainingClass(repositoryInterface.resolve());
methodParameter.initParameterNameDiscovery(nameDiscoverer); methodParameter.initParameterNameDiscovery(nameDiscoverer);
ResolvableType resolvableParameterType = ResolvableType.forMethodParameter(methodParameter, repositoryInterface); ResolvableType resolvableParameterType = ResolvableType.forMethodParameter(methodParameter);
TypeName parameterType = TypeName.get(resolvableParameterType.getType()); TypeName parameterType = TypeNames.resolvedTypeName(resolvableParameterType);
ParameterSpec parameterSpec = ParameterSpec.builder(parameterType, methodParameter.getParameterName()).build(); ParameterSpec parameterSpec = ParameterSpec.builder(parameterType, methodParameter.getParameterName()).build();

4
src/main/java/org/springframework/data/repository/aot/generate/MethodReturn.java

@ -61,7 +61,7 @@ public class MethodReturn {
this.returnedType = returnedType; this.returnedType = returnedType;
this.returnType = returnType; this.returnType = returnType;
this.typeName = TypeNames.typeName(returnType); this.typeName = TypeNames.resolvedTypeName(returnType);
this.className = TypeNames.className(returnType); this.className = TypeNames.className(returnType);
Class<?> returnClass = returnType.toClass(); Class<?> returnClass = returnType.toClass();
@ -72,7 +72,7 @@ public class MethodReturn {
if (actualType != null) { if (actualType != null) {
this.actualType = actualType.toResolvableType(); this.actualType = actualType.toResolvableType();
this.actualTypeName = TypeNames.typeName(this.actualType); this.actualTypeName = TypeNames.resolvedTypeName(this.actualType);
this.actualClassName = TypeNames.className(this.actualType); this.actualClassName = TypeNames.className(this.actualType);
this.actualReturnClass = actualType.getType(); this.actualReturnClass = actualType.getType();
} else { } else {

28
src/test/java/example/BaseRepository.java

@ -0,0 +1,28 @@
/*
* Copyright 2025-present 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
*
* http://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 example;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.NoRepositoryBean;
/**
* @author Christoph Strobl
*/
@NoRepositoryBean
public interface BaseRepository<T, ID> extends CrudRepository<T, ID> {
T findInBaseRepository(ID id);
}

2
src/test/java/example/UserRepository.java

@ -24,7 +24,7 @@ import org.springframework.data.repository.CrudRepository;
/** /**
* @author Christoph Strobl * @author Christoph Strobl
*/ */
public interface UserRepository extends CrudRepository<User, Long>, UserRepositoryExtension { public interface UserRepository extends BaseRepository<User, Long>, UserRepositoryExtension {
User findByFirstname(String firstname); User findByFirstname(String firstname);

110
src/test/java/org/springframework/data/javapoet/TypeNamesUnitTests.java

@ -17,15 +17,24 @@ package org.springframework.data.javapoet;
import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource; import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.core.MethodParameter;
import org.springframework.core.ResolvableType; import org.springframework.core.ResolvableType;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.GeoResult;
import org.springframework.data.geo.Point;
import org.springframework.javapoet.ClassName;
import org.springframework.javapoet.ParameterizedTypeName; import org.springframework.javapoet.ParameterizedTypeName;
import org.springframework.javapoet.TypeName; import org.springframework.javapoet.TypeName;
import org.springframework.javapoet.TypeVariableName;
import org.springframework.util.ReflectionUtils;
/** /**
* @author Christoph Strobl * @author Christoph Strobl
@ -59,4 +68,105 @@ class TypeNamesUnitTests {
assertThat(TypeNames.className(resolvableType)).isEqualTo(expected); assertThat(TypeNames.className(resolvableType)).isEqualTo(expected);
} }
@Test // GH-3374
void resolvedTypeNamesWithoutGenerics() {
ResolvableType resolvableType = ResolvableType.forClass(List.class);
assertThat(TypeNames.resolvedTypeName(resolvableType)).extracting(TypeName::toString).isEqualTo("java.util.List");
}
@Test // GH-3374
void resolvedTypeNamesForMethodParameters() {
ReflectionUtils.doWithMethods(Concrete.class, method -> {
if (!method.getName().contains("baseMethod")) {
return;
}
MethodParameter refiedObjectMethodParameter = new MethodParameter(method, 0).withContainingClass(Concrete.class);
ResolvableType resolvedObjectParameterType = ResolvableType.forMethodParameter(refiedObjectMethodParameter);
assertThat(TypeNames.typeName(resolvedObjectParameterType)).isEqualTo(TypeVariableName.get("T"));
assertThat(TypeNames.resolvedTypeName(resolvedObjectParameterType)).isEqualTo(TypeName.get(MyType.class));
MethodParameter refiedCollectionMethodParameter = new MethodParameter(method, 1)
.withContainingClass(Concrete.class);
ResolvableType resolvedCollectionParameterType = ResolvableType
.forMethodParameter(refiedCollectionMethodParameter);
assertThat(TypeNames.typeName(resolvedCollectionParameterType))
.isEqualTo(ParameterizedTypeName.get(ClassName.get(java.util.List.class), TypeVariableName.get("T")));
assertThat(TypeNames.resolvedTypeName(resolvedCollectionParameterType))
.isEqualTo(ParameterizedTypeName.get(java.util.List.class, MyType.class));
MethodParameter refiedArrayMethodParameter = new MethodParameter(method, 2).withContainingClass(Concrete.class);
ResolvableType resolvedArrayParameterType = ResolvableType.forMethodParameter(refiedArrayMethodParameter);
assertThat(TypeNames.typeName(resolvedArrayParameterType)).extracting(TypeName::toString).isEqualTo("T[]");
assertThat(TypeNames.resolvedTypeName(resolvedArrayParameterType)).extracting(TypeName::toString)
.isEqualTo("org.springframework.data.javapoet.TypeNamesUnitTests.MyType[]");
ResolvableType resolvedReturnType = ResolvableType.forMethodReturnType(method, Concrete.class);
assertThat(TypeNames.typeName(resolvedReturnType))
.isEqualTo(ParameterizedTypeName.get(ClassName.get(java.util.List.class), TypeVariableName.get("T")));
assertThat(TypeNames.resolvedTypeName(resolvedReturnType))
.isEqualTo(ParameterizedTypeName.get(java.util.List.class, MyType.class));
});
ReflectionUtils.doWithMethods(Concrete.class, method -> {
if (!method.getName().contains("otherMethod")) {
return;
}
MethodParameter refiedObjectMethodParameter = new MethodParameter(method, 0).withContainingClass(Concrete.class);
ResolvableType resolvedObjectParameterType = ResolvableType.forMethodParameter(refiedObjectMethodParameter);
assertThat(TypeNames.typeName(resolvedObjectParameterType)).isEqualTo(TypeVariableName.get("RT"));
assertThat(TypeNames.resolvedTypeName(resolvedObjectParameterType)).isEqualTo(TypeName.get(Object.class));
MethodParameter refiedCollectionMethodParameter = new MethodParameter(method, 1)
.withContainingClass(Concrete.class);
ResolvableType resolvedCollectionParameterType = ResolvableType
.forMethodParameter(refiedCollectionMethodParameter);
assertThat(TypeNames.typeName(resolvedCollectionParameterType))
.isEqualTo(ParameterizedTypeName.get(ClassName.get(java.util.List.class), TypeVariableName.get("RT")));
assertThat(TypeNames.resolvedTypeName(resolvedCollectionParameterType))
.isEqualTo(ClassName.get(java.util.List.class));
MethodParameter refiedArrayMethodParameter = new MethodParameter(method, 2).withContainingClass(Concrete.class);
ResolvableType resolvedArrayParameterType = ResolvableType.forMethodParameter(refiedArrayMethodParameter);
assertThat(TypeNames.typeName(resolvedArrayParameterType)).extracting(TypeName::toString).isEqualTo("RT[]");
assertThat(TypeNames.resolvedTypeName(resolvedArrayParameterType)).extracting(TypeName::toString)
.isEqualTo("java.lang.Object[]");
ResolvableType resolvedReturnType = ResolvableType.forMethodReturnType(method, Concrete.class);
assertThat(TypeNames.typeName(resolvedReturnType)).extracting(TypeName::toString).isEqualTo("RT");
assertThat(TypeNames.resolvedTypeName(resolvedReturnType)).isEqualTo(TypeName.get(Object.class));
});
ReflectionUtils.doWithMethods(Concrete.class, method -> {
if (!method.getName().contains("findByLocationNear")) {
return;
}
ResolvableType resolvedReturnType = ResolvableType.forMethodReturnType(method, Concrete.class);
assertThat(TypeNames.typeName(resolvedReturnType)).extracting(TypeName::toString).isEqualTo(
"java.util.List<org.springframework.data.geo.GeoResult<org.springframework.data.javapoet.TypeNamesUnitTests.MyType>>");
assertThat(TypeNames.resolvedTypeName(resolvedReturnType)).isEqualTo(ParameterizedTypeName
.get(ClassName.get(java.util.List.class), ParameterizedTypeName.get(GeoResult.class, MyType.class)));
});
}
interface GenericBase<T> {
java.util.List<T> baseMethod(T arg0, java.util.List<T> arg1, T... arg2);
<RT> RT otherMethod(RT arg0, java.util.List<RT> arg1, RT... arg2);
}
interface Concrete extends GenericBase<MyType> {
List<GeoResult<MyType>> findByLocationNear(Point point, Distance maxDistance);
}
static class MyType {}
} }

46
src/test/java/org/springframework/data/repository/aot/generate/AotRepositoryCreatorUnitTests.java

@ -15,12 +15,14 @@
*/ */
package org.springframework.data.repository.aot.generate; package org.springframework.data.repository.aot.generate;
import static org.assertj.core.api.Assertions.*; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.*; import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import example.UserRepository.User; import example.UserRepository.User;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.TimeZone; import java.util.TimeZone;
import javax.lang.model.element.Modifier; import javax.lang.model.element.Modifier;
@ -28,7 +30,6 @@ import javax.lang.model.element.Modifier;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.mockito.Answers; import org.mockito.Answers;
import org.springframework.aot.generate.Generated; import org.springframework.aot.generate.Generated;
import org.springframework.aot.hint.TypeReference; import org.springframework.aot.hint.TypeReference;
import org.springframework.core.ResolvableType; import org.springframework.core.ResolvableType;
@ -40,6 +41,7 @@ import org.springframework.data.repository.config.AotRepositoryInformation;
import org.springframework.data.repository.core.RepositoryInformation; import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.support.AnnotationRepositoryMetadata; import org.springframework.data.repository.core.support.AnnotationRepositoryMetadata;
import org.springframework.data.repository.core.support.RepositoryFragment; import org.springframework.data.repository.core.support.RepositoryFragment;
import org.springframework.data.repository.query.DefaultParameters;
import org.springframework.data.repository.query.QueryMethod; import org.springframework.data.repository.query.QueryMethod;
import org.springframework.javapoet.ClassName; import org.springframework.javapoet.ClassName;
import org.springframework.javapoet.JavaFile; import org.springframework.javapoet.JavaFile;
@ -179,8 +181,8 @@ class AotRepositoryCreatorUnitTests {
AnnotationRepositoryMetadata.getMetadata(QuerydslUserRepository.class), CrudRepository.class, AnnotationRepositoryMetadata.getMetadata(QuerydslUserRepository.class), CrudRepository.class,
List.of(RepositoryFragment.structural(QuerydslPredicateExecutor.class, DummyQuerydslPredicateExecutor.class))); List.of(RepositoryFragment.structural(QuerydslPredicateExecutor.class, DummyQuerydslPredicateExecutor.class)));
AotRepositoryCreator creator = AotRepositoryCreator AotRepositoryCreator creator = AotRepositoryCreator.forRepository(repositoryInformation, "Commons",
.forRepository(repositoryInformation, "Commons", new SpelAwareProxyProjectionFactory()); new SpelAwareProxyProjectionFactory());
creator.contributeMethods(method -> null); creator.contributeMethods(method -> null);
AotRepositoryCreator.AotBundle bundle = doCreate(creator); AotRepositoryCreator.AotBundle bundle = doCreate(creator);
@ -229,6 +231,38 @@ class AotRepositoryCreatorUnitTests {
"public %s(List<Metric> param1, String param2, Object ctorScoped)".formatted(targetType.getSimpleName())); "public %s(List<Metric> param1, String param2, Object ctorScoped)".formatted(targetType.getSimpleName()));
} }
@Test // GH-3374
void skipsMethodWithUnresolvableGenericReturnType() {
SpelAwareProxyProjectionFactory spelAwareProxyProjectionFactory = new SpelAwareProxyProjectionFactory();
AotRepositoryInformation repositoryInformation = new AotRepositoryInformation(
AnnotationRepositoryMetadata.getMetadata(UserRepository.class), CrudRepository.class,
List.of(RepositoryFragment.structural(QuerydslPredicateExecutor.class, DummyQuerydslPredicateExecutor.class)));
AotRepositoryCreator repositoryCreator = AotRepositoryCreator.forRepository(repositoryInformation, "Commons",
spelAwareProxyProjectionFactory);
repositoryCreator.contributeMethods(method -> {
QueryMethod queryMethod = new QueryMethod(method, repositoryInformation, spelAwareProxyProjectionFactory,
DefaultParameters::new);
return new MethodContributor<>(queryMethod, Map::of) {
@Override
public MethodSpec contribute(AotQueryMethodGenerationContext context) {
return MethodSpec.methodBuilder(context.getMethod().getName()).addCode("// 1 = 1").build();
}
@Override
public boolean contributesMethodSpec() {
return true;
}
};
});
// same package as source repo
assertThat(generate(repositoryCreator)).contains("someMethod()").doesNotContain("findByFirstname()");
}
private AotRepositoryCreator.AotBundle doCreate(AotRepositoryCreator creator) { private AotRepositoryCreator.AotBundle doCreate(AotRepositoryCreator creator) {
return creator.create(getTypeSpecBuilder(creator)); return creator.create(getTypeSpecBuilder(creator));
} }
@ -263,6 +297,8 @@ class AotRepositoryCreatorUnitTests {
interface UserRepository extends org.springframework.data.repository.Repository<User, String> { interface UserRepository extends org.springframework.data.repository.Repository<User, String> {
String someMethod(); String someMethod();
<T> List<T> findByFirstname(String firstname, Class<T> type);
} }
interface QuerydslUserRepository interface QuerydslUserRepository

7
src/test/java/org/springframework/data/repository/aot/generate/AotRepositoryMethodBuilderUnitTests.java

@ -60,7 +60,6 @@ class AotRepositoryMethodBuilderUnitTests {
Method method = UserRepository.class.getMethod("findByFirstname", String.class); Method method = UserRepository.class.getMethod("findByFirstname", String.class);
when(methodGenerationContext.getMethod()).thenReturn(method); when(methodGenerationContext.getMethod()).thenReturn(method);
when(methodGenerationContext.getReturnType()).thenReturn(ResolvableType.forClass(User.class));
doReturn(TypeInformation.of(User.class)).when(repositoryInformation).getReturnType(any()); doReturn(TypeInformation.of(User.class)).when(repositoryInformation).getReturnType(any());
doReturn(TypeInformation.of(User.class)).when(repositoryInformation).getReturnedDomainTypeInformation(any()); doReturn(TypeInformation.of(User.class)).when(repositoryInformation).getReturnedDomainTypeInformation(any());
MethodMetadata methodMetadata = new MethodMetadata(repositoryInformation, method); MethodMetadata methodMetadata = new MethodMetadata(repositoryInformation, method);
@ -76,11 +75,10 @@ class AotRepositoryMethodBuilderUnitTests {
Method method = UserRepository.class.getMethod("findByFirstnameIn", List.class); Method method = UserRepository.class.getMethod("findByFirstnameIn", List.class);
when(methodGenerationContext.getMethod()).thenReturn(method); when(methodGenerationContext.getMethod()).thenReturn(method);
when(methodGenerationContext.getReturnType())
.thenReturn(ResolvableType.forClassWithGenerics(List.class, User.class));
doReturn(TypeInformation.of(User.class)).when(repositoryInformation).getReturnType(any()); doReturn(TypeInformation.of(User.class)).when(repositoryInformation).getReturnType(any());
doReturn(TypeInformation.of(User.class)).when(repositoryInformation).getReturnedDomainTypeInformation(any()); doReturn(TypeInformation.of(User.class)).when(repositoryInformation).getReturnedDomainTypeInformation(any());
MethodMetadata methodMetadata = new MethodMetadata(repositoryInformation, method); MethodMetadata methodMetadata = spy(new MethodMetadata(repositoryInformation, method));
when(methodMetadata.getReturnType()).thenReturn(ResolvableType.forClassWithGenerics(List.class, User.class));
when(methodGenerationContext.getTargetMethodMetadata()).thenReturn(methodMetadata); when(methodGenerationContext.getTargetMethodMetadata()).thenReturn(methodMetadata);
AotRepositoryMethodBuilder builder = new AotRepositoryMethodBuilder(methodGenerationContext); AotRepositoryMethodBuilder builder = new AotRepositoryMethodBuilder(methodGenerationContext);
@ -95,7 +93,6 @@ class AotRepositoryMethodBuilderUnitTests {
Method method = UserRepository.class.getMethod("findByFirstname", String.class); Method method = UserRepository.class.getMethod("findByFirstname", String.class);
when(methodGenerationContext.getMethod()).thenReturn(method); when(methodGenerationContext.getMethod()).thenReturn(method);
when(methodGenerationContext.getReturnType()).thenReturn(ResolvableType.forClass(User.class));
doReturn(TypeInformation.of(User.class)).when(repositoryInformation).getReturnType(any()); doReturn(TypeInformation.of(User.class)).when(repositoryInformation).getReturnType(any());
doReturn(TypeInformation.of(User.class)).when(repositoryInformation).getReturnedDomainTypeInformation(any()); doReturn(TypeInformation.of(User.class)).when(repositoryInformation).getReturnedDomainTypeInformation(any());
MethodMetadata methodMetadata = new MethodMetadata(repositoryInformation, method); MethodMetadata methodMetadata = new MethodMetadata(repositoryInformation, method);

Loading…
Cancel
Save