Browse Source

Polishing.

Remove test-only methods and move these into the tests. Improve encapsulation.

Move AotRepositoryBeanDefinitionPropertiesDecorator from config to aot.generate package due to its strong coupling with aot.generate packages.

See: #3344
Original pull request: #3351
issue/3353
Mark Paluch 4 months ago
parent
commit
09a8d9d919
No known key found for this signature in database
GPG Key ID: 55BC6374BAA9D973
  1. 16
      src/main/java/org/springframework/data/repository/aot/generate/AotQueryMethodGenerationContext.java
  2. 64
      src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryBeanDefinitionPropertiesDecorator.java
  3. 19
      src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryConstructorBuilder.java
  4. 239
      src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryCreator.java
  5. 37
      src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryFragmentMetadata.java
  6. 5
      src/main/java/org/springframework/data/repository/aot/generate/MethodContributor.java
  7. 28
      src/main/java/org/springframework/data/repository/aot/generate/RepositoryConstructorBuilder.java
  8. 33
      src/main/java/org/springframework/data/repository/aot/generate/RepositoryContributor.java
  9. 23
      src/main/java/org/springframework/data/repository/config/RepositoryRegistrationAotContribution.java
  10. 12
      src/test/java/org/springframework/data/repository/aot/generate/AotRepositoryBeanDefinitionPropertiesDecoratorUnitTests.java
  11. 13
      src/test/java/org/springframework/data/repository/aot/generate/AotRepositoryConfigurationUnitTests.java
  12. 72
      src/test/java/org/springframework/data/repository/aot/generate/AotRepositoryCreatorUnitTests.java
  13. 12
      src/test/java/org/springframework/data/repository/aot/generate/RepositoryContributorUnitTests.java

16
src/main/java/org/springframework/data/repository/aot/generate/AotQueryMethodGenerationContext.java

@ -52,7 +52,20 @@ public class AotQueryMethodGenerationContext { @@ -52,7 +52,20 @@ public class AotQueryMethodGenerationContext {
private final ExpressionMarker expressionMarker;
protected AotQueryMethodGenerationContext(RepositoryInformation repositoryInformation, Method method,
QueryMethod queryMethod, AotRepositoryFragmentMetadata targetTypeMetadata) {
QueryMethod queryMethod) {
this.method = method;
this.annotations = MergedAnnotations.from(method);
this.queryMethod = queryMethod;
this.repositoryInformation = repositoryInformation;
this.targetTypeMetadata = new AotRepositoryFragmentMetadata();
this.targetMethodMetadata = new MethodMetadata(repositoryInformation, method);
this.variableNameFactory = LocalVariableNameFactory.forMethod(targetMethodMetadata);
this.expressionMarker = new ExpressionMarker();
}
AotQueryMethodGenerationContext(RepositoryInformation repositoryInformation, Method method, QueryMethod queryMethod,
AotRepositoryFragmentMetadata targetTypeMetadata) {
this.method = method;
this.annotations = MergedAnnotations.from(method);
@ -352,4 +365,5 @@ public class AotQueryMethodGenerationContext { @@ -352,4 +365,5 @@ public class AotQueryMethodGenerationContext {
public ExpressionMarker getExpressionMarker() {
return expressionMarker;
}
}

64
src/main/java/org/springframework/data/repository/config/AotRepositoryBeanDefinitionPropertiesDecorator.java → src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryBeanDefinitionPropertiesDecorator.java

@ -13,20 +13,23 @@ @@ -13,20 +13,23 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.repository.config;
package org.springframework.data.repository.aot.generate;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.function.Supplier;
import javax.lang.model.element.Modifier;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.core.ResolvableType;
import org.springframework.data.repository.aot.generate.RepositoryContributor;
import org.springframework.data.repository.core.support.RepositoryComposition;
import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
import org.springframework.javapoet.CodeBlock;
import org.springframework.javapoet.MethodSpec;
import org.springframework.javapoet.TypeName;
import org.springframework.javapoet.TypeSpec;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@ -39,7 +42,7 @@ import org.springframework.util.StringUtils; @@ -39,7 +42,7 @@ import org.springframework.util.StringUtils;
* @author Christoph Strobl
* @since 4.0
*/
class AotRepositoryBeanDefinitionPropertiesDecorator {
public class AotRepositoryBeanDefinitionPropertiesDecorator {
private static final Map<ResolvableType, String> RESERVED_TYPES;
@ -57,10 +60,13 @@ class AotRepositoryBeanDefinitionPropertiesDecorator { @@ -57,10 +60,13 @@ class AotRepositoryBeanDefinitionPropertiesDecorator {
* @param inheritedProperties bean definition code (containing properties and such) already added via another
* component.
* @param repositoryContributor the contributor providing the actual AOT repository implementation.
* @throws IllegalArgumentException if {@link RepositoryContributor#getContributedTypeName()} is not set.
*/
public AotRepositoryBeanDefinitionPropertiesDecorator(Supplier<CodeBlock> inheritedProperties,
RepositoryContributor repositoryContributor) {
Assert.notNull(repositoryContributor.getContributedTypeName(), "Contributed type name must not be null");
this.inheritedProperties = inheritedProperties;
this.repositoryContributor = repositoryContributor;
}
@ -73,54 +79,58 @@ class AotRepositoryBeanDefinitionPropertiesDecorator { @@ -73,54 +79,58 @@ class AotRepositoryBeanDefinitionPropertiesDecorator {
* needs to have potential constructor arguments resolved.
*
* @return the decorated code block.
* @throws IllegalArgumentException if {@link RepositoryContributor#getContributedTypeName()} is not set.
*/
public CodeBlock decorate() {
Assert.notNull(repositoryContributor.getContributedTypeName(), "Contributed type name must not be null");
CodeBlock.Builder builder = CodeBlock.builder();
// bring in properties as usual
builder.add(inheritedProperties.get());
builder.add("beanDefinition.getPropertyValues().addPropertyValue(\"repositoryFragmentsFunction\", new $T() {\n",
RepositoryFactoryBeanSupport.RepositoryFragmentsFunction.class);
builder.indent();
MethodSpec.Builder callbackMethod = MethodSpec.methodBuilder("getRepositoryFragments").addModifiers(Modifier.PUBLIC)
.returns(RepositoryComposition.RepositoryFragments.class);
builder.add("public $T getRepositoryFragments(", RepositoryComposition.RepositoryFragments.class);
int counter = 0;
for (Entry<ResolvableType, String> entry : RESERVED_TYPES.entrySet()) {
builder.add("$T $L", entry.getKey().toClass(), entry.getValue());
if (++counter < RESERVED_TYPES.size()) {
builder.add(", ");
}
callbackMethod.addParameter(entry.getKey().toClass(), entry.getValue());
}
builder.add(") {\n");
builder.indent();
callbackMethod.addCode(buildCallbackBody());
TypeSpec repositoryFragmentsFunction = TypeSpec.anonymousClassBuilder("")
.superclass(RepositoryFactoryBeanSupport.RepositoryFragmentsFunction.class).addMethod(callbackMethod.build())
.build();
for (Map.Entry<String, ResolvableType> entry : repositoryContributor.requiredArgs().entrySet()) {
builder.addStatement("beanDefinition.getPropertyValues().addPropertyValue($S, $L)", "repositoryFragmentsFunction",
repositoryFragmentsFunction);
return builder.build();
}
private CodeBlock buildCallbackBody() {
CodeBlock.Builder callback = CodeBlock.builder();
for (Entry<String, ResolvableType> entry : repositoryContributor.requiredArgs().entrySet()) {
TypeName argumentType = TypeName.get(entry.getValue().getType());
String reservedArgumentName = RESERVED_TYPES.get(entry.getValue());
if (reservedArgumentName == null) {
builder.addStatement("$1T $2L = beanFactory.getBean($1T.class)", argumentType, entry.getKey());
callback.addStatement("$1T $2L = beanFactory.getBean($1T.class)", argumentType, entry.getKey());
} else {
if (!reservedArgumentName.equals(entry.getKey())) {
builder.addStatement("$T $L = $L", argumentType, entry.getKey(), reservedArgumentName);
if (reservedArgumentName.equals(entry.getKey())) {
continue;
}
callback.addStatement("$T $L = $L", argumentType, entry.getKey(), reservedArgumentName);
}
}
builder.addStatement("return RepositoryComposition.RepositoryFragments.just(new $L($L))",
callback.addStatement("return $T.just(new $L($L))", RepositoryComposition.RepositoryFragments.class,
repositoryContributor.getContributedTypeName().getCanonicalName(),
StringUtils.collectionToDelimitedString(repositoryContributor.requiredArgs().keySet(), ", "));
builder.unindent();
builder.add("}\n");
builder.unindent();
builder.add("});\n");
return builder.build();
return callback.build();
}
}

19
src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryConstructorBuilder.java

@ -17,7 +17,6 @@ package org.springframework.data.repository.aot.generate; @@ -17,7 +17,6 @@ package org.springframework.data.repository.aot.generate;
import org.springframework.core.ResolvableType;
import org.springframework.javapoet.CodeBlock;
import org.springframework.javapoet.MethodSpec;
/**
* Builder for AOT Repository Constructors.
@ -34,7 +33,9 @@ public interface AotRepositoryConstructorBuilder { @@ -34,7 +33,9 @@ public interface AotRepositoryConstructorBuilder {
* @param parameterName name of the parameter.
* @param type parameter type.
*/
void addParameter(String parameterName, Class<?> type);
default void addParameter(String parameterName, Class<?> type) {
addParameter(parameterName, ResolvableType.forClass(type));
}
/**
* Add constructor parameter and create a field storing its value.
@ -51,10 +52,10 @@ public interface AotRepositoryConstructorBuilder { @@ -51,10 +52,10 @@ public interface AotRepositoryConstructorBuilder {
*
* @param parameterName name of the parameter.
* @param type parameter type.
* @param createField whether to create a field for the parameter and assign its value to the field.
* @param bindToField whether to create a field for the parameter and assign its value to the field.
*/
default void addParameter(String parameterName, Class<?> type, boolean createField) {
addParameter(parameterName, ResolvableType.forClass(type), createField);
default void addParameter(String parameterName, Class<?> type, boolean bindToField) {
addParameter(parameterName, ResolvableType.forClass(type), bindToField);
}
/**
@ -62,15 +63,15 @@ public interface AotRepositoryConstructorBuilder { @@ -62,15 +63,15 @@ public interface AotRepositoryConstructorBuilder {
*
* @param parameterName name of the parameter.
* @param type parameter type.
* @param createField whether to create a field for the parameter and assign its value to the field.
* @param bindToField whether to create a field for the parameter and assign its value to the field.
*/
void addParameter(String parameterName, ResolvableType type, boolean createField);
void addParameter(String parameterName, ResolvableType type, boolean bindToField);
/**
* Add constructor customizer. Customizer is invoked after adding constructor arguments and before assigning
* Add constructor body customizer. The customizer is invoked after adding constructor arguments and before assigning
* constructor arguments to fields.
*
* @param customizer the customizer with direct access to the {@link MethodSpec.Builder constructor builder}.
* @param customizer the customizer with direct access to the {@link CodeBlock.Builder constructor builder}.
*/
void customize(ConstructorCustomizer customizer);

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

@ -29,9 +29,7 @@ import javax.lang.model.element.Modifier; @@ -29,9 +29,7 @@ import javax.lang.model.element.Modifier;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jspecify.annotations.Nullable;
import org.springframework.aot.generate.Generated;
import org.springframework.aot.generate.GeneratedTypeReference;
import org.springframework.aot.hint.TypeReference;
import org.springframework.core.ResolvableType;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.repository.aot.generate.AotRepositoryFragmentMetadata.ConstructorArgument;
@ -42,7 +40,6 @@ import org.springframework.data.repository.query.QueryMethod; @@ -42,7 +40,6 @@ import org.springframework.data.repository.query.QueryMethod;
import org.springframework.data.util.Lazy;
import org.springframework.javapoet.ClassName;
import org.springframework.javapoet.FieldSpec;
import org.springframework.javapoet.JavaFile;
import org.springframework.javapoet.MethodSpec;
import org.springframework.javapoet.TypeSpec;
import org.springframework.util.Assert;
@ -90,89 +87,53 @@ class AotRepositoryCreator { @@ -90,89 +87,53 @@ class AotRepositoryCreator {
}
/**
* Configure a {@link AotRepositoryConstructorBuilder} customizer.
* Get the {@link ClassName} for the AOT repository fragment.
*
* @param classCustomizer must not be {@literal null}.
* @return {@code this}.
* @return the {@link ClassName} for the AOT repository fragment.
*/
AotRepositoryCreator customizeClass(Consumer<AotRepositoryClassBuilder> classCustomizer) {
this.classCustomizer = classCustomizer;
return this;
ClassName getClassName() {
return ClassName.get(packageName(),
"%sImpl".formatted(repositoryInformation.getRepositoryInterface().getSimpleName()));
}
/**
* Configure a {@link AotRepositoryConstructorBuilder} customizer.
*
* @param constructorCustomizer must not be {@literal null}.
* @return {@code this}.
*/
@SuppressWarnings("NullAway")
AotRepositoryCreator customizeConstructor(Consumer<AotRepositoryConstructorBuilder> constructorCustomizer) {
String packageName() {
return repositoryInformation.getRepositoryInterface().getPackageName();
}
if (constructorBuilder != null) {
constructorBuilder.dispose();
Map<String, ResolvableType> getAutowireFields() {
Map<String, ResolvableType> autowireFields = new LinkedHashMap<>(
generationMetadata.getConstructorArguments().size());
for (Map.Entry<String, ConstructorArgument> entry : generationMetadata.getConstructorArguments().entrySet()) {
autowireFields.put(entry.getKey(), entry.getValue().parameterType());
}
return autowireFields;
}
RepositoryConstructorBuilder constructorBuilder = new RepositoryConstructorBuilder(generationMetadata);
constructorCustomizer.accept(constructorBuilder);
this.constructorBuilder = constructorBuilder;
return this;
RepositoryInformation getRepositoryInformation() {
return repositoryInformation;
}
AotRepositoryCreator resolveQueryMethods() {
return resolveQueryMethods(new MethodContributorFactory() {
@Override
public @Nullable MethodContributor<? extends QueryMethod> create(Method method) {
return null;
}
});
ProjectionFactory getProjectionFactory() {
return projectionFactory;
}
/**
* Configure a {@link MethodContributor} factory.
* Create the AOT repository fragment and add constructors, methods and fields to the given {@link TypeSpec.Builder}.
*
* @param methodContributorFactory must not be {@literal null}.
* @return {@code this}.
* @param target the target {@link TypeSpec.Builder} to which the AOT repository fragment will be added.
* @return an
*/
AotRepositoryCreator resolveQueryMethods(@Nullable MethodContributorFactory methodContributorFactory) {
Arrays.stream(repositoryInformation.getRepositoryInterface().getMethods())
.sorted(Comparator.<Method, String> comparing(it -> {
return it.getDeclaringClass().getName();
}).thenComparing(Method::getName).thenComparing(Method::getParameterCount).thenComparing(Method::toString))
.forEach(method -> {
RepositoryComposition repositoryComposition = repositoryInformation.getRepositoryComposition();
try {
resolveQueryMethod(method, methodContributorFactory, repositoryComposition, generationMetadata);
} catch (RuntimeException e) {
if (logger.isErrorEnabled()) {
logger.error("Failed to contribute Repository method [%s.%s]"
.formatted(repositoryInformation.getRepositoryInterface().getName(), method.getName()), e);
}
}
});
return this;
}
AotBundle create() {
return create(repositoryImplementationTypeName());
}
AotBundle create(String targetTypeName) {
return create(TypeSpec.classBuilder(ClassName.bestGuess(targetTypeName)).addAnnotation(Generated.class));
}
AotBundle create(TypeSpec.Builder builder) {
AotBundle create(TypeSpec.Builder target) {
List<AotRepositoryMethod> methodMetadata = new ArrayList<>();
builder.addModifiers(Modifier.PUBLIC) //
target.addModifiers(Modifier.PUBLIC) //
.addJavadoc("AOT generated $L repository implementation for {@link $T}.\n", moduleName,
repositoryInformation.getRepositoryInterface());
// create the constructor
builder.addMethod(buildConstructor());
target.addMethod(buildConstructor());
generationMetadata.getMethods().values().forEach(localMethod -> {
@ -182,7 +143,7 @@ class AotRepositoryCreator { @@ -182,7 +143,7 @@ class AotRepositoryCreator {
MethodSpec methodSpec = methodContributor.contribute(context);
if (methodSpec != null) {
builder.addMethod(methodSpec);
target.addMethod(methodSpec);
}
// TODO: decouple json from method building and get rid of methodMetadata here?
@ -209,24 +170,19 @@ class AotRepositoryCreator { @@ -209,24 +170,19 @@ class AotRepositoryCreator {
// write fields at the end so we make sure to capture things added by methods
generationMetadata.getFields().values().stream()
.map(field -> FieldSpec.builder(field.fieldType().getType(), field.fieldName(), field.modifiers()).build())
.forEach(builder::addField);
.forEach(target::addField);
// finally customize the file itself
this.classCustomizer.accept(customizer -> {
Assert.notNull(customizer, "ClassCustomizer must not be null");
customizer.customize(builder);
customizer.customize(target);
});
return new AotBundle(repositoryInformation.getRepositoryInterface(),
Lazy.of(() -> JavaFile.builder(packageName(), builder.build()).build()),
Lazy.of(() -> getAotRepositoryMetadata(methodMetadata)));
}
String repositoryImplementationTypeName() {
return "%s.%s".formatted(packageName(), typeName());
}
private MethodSpec buildConstructor() {
return constructorBuilder != null ? constructorBuilder.buildConstructor()
: MethodSpec.constructorBuilder().addModifiers(Modifier.PUBLIC).build();
@ -244,63 +200,117 @@ class AotRepositoryCreator { @@ -244,63 +200,117 @@ class AotRepositoryCreator {
repositoryType, methodMetadata);
}
private void resolveQueryMethod(Method method, @Nullable MethodContributorFactory contributorFactory,
RepositoryComposition repositoryComposition, AotRepositoryFragmentMetadata metadata) {
/**
* Configure a {@link AotRepositoryConstructorBuilder} customizer.
*
* @param classCustomizer must not be {@literal null}.
* @return {@code this}.
*/
AotRepositoryCreator customizeClass(Consumer<AotRepositoryClassBuilder> classCustomizer) {
this.classCustomizer = classCustomizer;
return this;
}
/**
* Configure a {@link AotRepositoryConstructorBuilder} customizer.
*
* @param constructorCustomizer must not be {@literal null}.
* @return {@code this}.
*/
@SuppressWarnings("NullAway")
AotRepositoryCreator customizeConstructor(Consumer<AotRepositoryConstructorBuilder> constructorCustomizer) {
if (constructorBuilder != null) {
constructorBuilder.dispose();
}
RepositoryConstructorBuilder constructorBuilder = new RepositoryConstructorBuilder(generationMetadata);
constructorCustomizer.accept(constructorBuilder);
this.constructorBuilder = constructorBuilder;
return this;
}
/**
* Contribute repository methods using {@link MethodContributor} factory.
*
* @param methodContributorFactory must not be {@literal null}.
*/
void contributeMethods(@Nullable MethodContributorFactory methodContributorFactory) {
Arrays.stream(repositoryInformation.getRepositoryInterface().getMethods())
.sorted(Comparator.<Method, String> comparing(it -> it.getDeclaringClass().getName()) //
.thenComparing(Method::getName) //
.thenComparing(Method::getParameterCount) //
.thenComparing(Method::toString))
.forEach(method -> {
try {
contributeMethod(method, methodContributorFactory);
} catch (RuntimeException e) {
if (logger.isErrorEnabled()) {
logger.error("Failed to contribute Repository method [%s.%s]"
.formatted(repositoryInformation.getRepositoryInterface().getName(), method.getName()), e);
}
}
});
}
private void contributeMethod(Method method, @Nullable MethodContributorFactory contributorFactory) {
if (repositoryInformation.isCustomMethod(method)
|| (repositoryInformation.isBaseClassMethod(method) && !repositoryInformation.isQueryMethod(method))) {
RepositoryComposition repositoryComposition = repositoryInformation.getRepositoryComposition();
RepositoryFragment<?> fragment = repositoryComposition.findFragment(method);
if (fragment != null) {
metadata.addDelegateMethod(method, fragment);
generationMetadata.addDelegateMethod(method, fragment);
return;
}
}
if (method.isBridge() || method.isDefault() || java.lang.reflect.Modifier.isStatic(method.getModifiers())) {
if (logger.isTraceEnabled()) {
logger.trace("Skipping %s method [%s.%s] contribution".formatted(
(method.isBridge() ? "bridge" : method.isDefault() ? "default" : "static"),
repositoryInformation.getRepositoryInterface().getName(), method.getName()));
}
return;
}
if (repositoryInformation.isQueryMethod(method) && contributorFactory != null) {
if (!repositoryInformation.isQueryMethod(method)) {
MethodContributor<? extends QueryMethod> contributor = contributorFactory.create(method);
if (contributor != null) {
if (contributor.contributesMethodSpec() && !repositoryInformation.isReactiveRepository()) {
metadata.addRepositoryMethod(method, contributor);
} else {
metadata.addDelegateMethod(method, contributor);
}
if (logger.isTraceEnabled()) {
logger.trace("Skipping method [%s.%s] contribution, not a query method"
.formatted(repositoryInformation.getRepositoryInterface().getName(), method.getName()));
}
return;
}
}
public String packageName() {
return repositoryInformation.getRepositoryInterface().getPackageName();
}
if (contributorFactory == null) {
public String typeName() {
return "%sImpl".formatted(repositoryInformation.getRepositoryInterface().getSimpleName());
}
if (logger.isTraceEnabled()) {
logger.trace("Skipping method [%s.%s] contribution, no MethodContributorFactory available"
.formatted(repositoryInformation.getRepositoryInterface().getName(), method.getName()));
}
return;
}
public Map<String, ResolvableType> getAutowireFields() {
MethodContributor<? extends QueryMethod> contributor = contributorFactory.create(method);
Map<String, ResolvableType> autowireFields = new LinkedHashMap<>(
generationMetadata.getConstructorArguments().size());
for (Map.Entry<String, ConstructorArgument> entry : generationMetadata.getConstructorArguments().entrySet()) {
autowireFields.put(entry.getKey(), entry.getValue().parameterType());
if (contributor == null) {
if (logger.isTraceEnabled()) {
logger.trace("Skipping method [%s.%s] contribution, no MethodContributor available"
.formatted(repositoryInformation.getRepositoryInterface().getName(), method.getName()));
}
}
return autowireFields;
}
public RepositoryInformation getRepositoryInformation() {
return repositoryInformation;
}
public ProjectionFactory getProjectionFactory() {
return projectionFactory;
if (contributor.contributesMethodSpec() && !repositoryInformation.isReactiveRepository()) {
generationMetadata.addRepositoryMethod(method, contributor);
} else {
generationMetadata.addDelegateMethod(method, contributor);
}
}
/**
@ -336,24 +346,11 @@ class AotRepositoryCreator { @@ -336,24 +346,11 @@ class AotRepositoryCreator {
}
record AotBundle(Class<?> sourceRepository, Lazy<JavaFile> javaFile, Lazy<AotRepositoryMetadata> metadata) {
record AotBundle(Class<?> sourceRepository, Lazy<AotRepositoryMetadata> metadata) {
String repositoryJsonFileName() {
return sourceRepository.getName().replace('.', '/') + ".json";
}
TypeReference generatedRepositoryTypeName() {
JavaFile file = javaFile.get();
return GeneratedTypeReference.of(ClassName.get(file.packageName(), file.typeSpec().name()));
}
String generatedCode() {
return javaFile().get().toString();
}
String generatedMetadata() {
return metadata().get().toJson().toString(2);
}
}
}

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

@ -37,17 +37,13 @@ import org.springframework.javapoet.TypeName; @@ -37,17 +37,13 @@ import org.springframework.javapoet.TypeName;
* @author Mark Paluch
* @since 4.0
*/
public class AotRepositoryFragmentMetadata {
class AotRepositoryFragmentMetadata {
private final Map<String, LocalField> fields = new HashMap<>(3);
private final Map<String, ConstructorArgument> constructorArguments = new LinkedHashMap<>(3);
private final Map<String, LocalMethod> methods = new LinkedHashMap<>();
private final Map<String, DelegateMethod> delegateMethods = new LinkedHashMap<>();
public AotRepositoryFragmentMetadata() {
}
/**
* Lookup a field name by exact type. Returns the first field that matches the type or {@literal null} if no field
* with that type was found.
@ -134,6 +130,19 @@ public class AotRepositoryFragmentMetadata { @@ -134,6 +130,19 @@ public class AotRepositoryFragmentMetadata {
return delegateMethods;
}
static TypeName typeNameOf(ResolvableType type) {
if (type.equals(ResolvableType.NONE)) {
return TypeName.get(Object.class);
}
if (!type.hasResolvableGenerics()) {
return TypeName.get(type.getType());
}
return ParameterizedTypeName.get(type.toClass(), type.resolveGenerics());
}
/**
* Constructor argument metadata.
*
@ -143,11 +152,6 @@ public class AotRepositoryFragmentMetadata { @@ -143,11 +152,6 @@ public class AotRepositoryFragmentMetadata {
*/
public record ConstructorArgument(String parameterName, ResolvableType parameterType, @Nullable String fieldName) {
@Deprecated(forRemoval = true)
boolean isForLocalField() {
return isBoundToField();
}
boolean isBoundToField() {
return fieldName != null;
}
@ -174,17 +178,4 @@ public class AotRepositoryFragmentMetadata { @@ -174,17 +178,4 @@ public class AotRepositoryFragmentMetadata {
}
static TypeName typeNameOf(ResolvableType type) {
if (type.equals(ResolvableType.NONE)) {
return TypeName.get(Object.class);
}
if (!type.hasResolvableGenerics()) {
return TypeName.get(type.getType());
}
return ParameterizedTypeName.get(type.toClass(), type.resolveGenerics());
}
}

5
src/main/java/org/springframework/data/repository/aot/generate/MethodContributor.java

@ -17,7 +17,6 @@ package org.springframework.data.repository.aot.generate; @@ -17,7 +17,6 @@ package org.springframework.data.repository.aot.generate;
import java.util.function.Consumer;
import org.jspecify.annotations.NonNull;
import org.jspecify.annotations.Nullable;
import org.springframework.data.repository.query.QueryMethod;
@ -65,6 +64,7 @@ public abstract class MethodContributor<M extends QueryMethod> { @@ -65,6 +64,7 @@ public abstract class MethodContributor<M extends QueryMethod> {
b.contribute(contribution::contribute);
});
}
};
}
@ -140,6 +140,7 @@ public abstract class MethodContributor<M extends QueryMethod> { @@ -140,6 +140,7 @@ public abstract class MethodContributor<M extends QueryMethod> {
public interface RepositoryMethodContribution {
CodeBlock contribute(AotQueryMethodGenerationContext context);
}
/**
@ -180,7 +181,7 @@ public abstract class MethodContributor<M extends QueryMethod> { @@ -180,7 +181,7 @@ public abstract class MethodContributor<M extends QueryMethod> {
}
@Override
public @NonNull MethodSpec contribute(AotQueryMethodGenerationContext context) {
public MethodSpec contribute(AotQueryMethodGenerationContext context) {
AotRepositoryMethodBuilder builder = new AotRepositoryMethodBuilder(context);
builderConsumer.accept(builder);

28
src/main/java/org/springframework/data/repository/aot/generate/RepositoryConstructorBuilder.java

@ -44,39 +44,17 @@ class RepositoryConstructorBuilder implements AotRepositoryConstructorBuilder { @@ -44,39 +44,17 @@ class RepositoryConstructorBuilder implements AotRepositoryConstructorBuilder {
this.metadata = metadata;
}
/**
* Add constructor parameter and create a field storing its value.
*
* @param parameterName name of the parameter.
* @param type parameter type.
*/
@Override
public void addParameter(String parameterName, Class<?> type) {
addParameter(parameterName, ResolvableType.forClass(type));
}
/**
* Add constructor parameter and create a field storing its value.
*
* @param parameterName name of the parameter.
* @param type parameter type.
*/
@Override
public void addParameter(String parameterName, ResolvableType type) {
addParameter(parameterName, type, true);
}
/**
* Add constructor parameter.
*
* @param parameterName name of the parameter.
* @param type parameter type.
* @param createField whether to create a field for the parameter and assign its value to the field.
* @param bindToField whether to create a field for the parameter and assign its value to the field.
*/
@Override
public void addParameter(String parameterName, ResolvableType type, boolean createField) {
public void addParameter(String parameterName, ResolvableType type, boolean bindToField) {
this.parametersAdded.add(parameterName);
this.metadata.addConstructorArgument(parameterName, type, createField ? parameterName : null);
this.metadata.addConstructorArgument(parameterName, type, bindToField ? parameterName : null);
}
/**

33
src/main/java/org/springframework/data/repository/aot/generate/RepositoryContributor.java

@ -21,6 +21,7 @@ import java.util.Collections; @@ -21,6 +21,7 @@ import java.util.Collections;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jspecify.annotations.Nullable;
import org.springframework.aot.generate.GeneratedClass;
import org.springframework.aot.generate.GeneratedTypeReference;
import org.springframework.aot.generate.GenerationContext;
@ -33,7 +34,7 @@ import org.springframework.data.repository.aot.generate.AotRepositoryCreator.Aot @@ -33,7 +34,7 @@ import org.springframework.data.repository.aot.generate.AotRepositoryCreator.Aot
import org.springframework.data.repository.config.AotRepositoryContext;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.query.QueryMethod;
import org.springframework.javapoet.ClassName;
import org.springframework.javapoet.JavaFile;
/**
* Contributor for AOT repository fragments.
@ -87,7 +88,8 @@ public class RepositoryContributor { @@ -87,7 +88,8 @@ public class RepositoryContributor {
* @return the {@link TypeReference} of the contributed type. Can be {@literal null} until
* {@link #contribute(GenerationContext)} is called to obtain the actual {@link GeneratedClass} instance.
*/
public @Nullable TypeReference getContributedTypeName() {
@Nullable
TypeReference getContributedTypeName() {
return this.contributedTypeName;
}
@ -97,25 +99,31 @@ public class RepositoryContributor { @@ -97,25 +99,31 @@ public class RepositoryContributor {
* during application startup.
* <p>
* Can be overridden if required. Needs to match arguments of generated repository implementation.
*
*
* @return key/value pairs of required argument required to instantiate the generated fragment.
*/
// TODO: should we switch from ResolvableType to some custom value object to cover qualifiers?
public java.util.Map<String, ResolvableType> requiredArgs() {
java.util.Map<String, ResolvableType> requiredArgs() {
return Collections.unmodifiableMap(creator.getAutowireFields());
}
/**
* Contribute the AOT repository fragment to the given {@link GenerationContext}. This method will prepare the
* metadata, generate the source code and write it to the {@link GenerationContext}.
*
* @param generationContext must not be {@literal null}.
*/
public final void contribute(GenerationContext generationContext) {
// prepare and collect metadata
creator.customizeClass(this::customizeClass) //
.customizeConstructor(this::customizeConstructor) //
.resolveQueryMethods(this::contributeQueryMethod); //
.contributeMethods(this::contributeQueryMethod); //
// obtain the generated type and its target name.
// Writing the source is triggered by DefaultGenerationContext#writeGeneratedContent() at a later stage
GeneratedClass generatedClass = generationContext.getGeneratedClasses().getOrAddForFeatureComponent(FEATURE_NAME,
ClassName.bestGuess(creator.repositoryImplementationTypeName()), targetTypeSpec -> {
creator.getClassName(), targetTypeSpec -> {
// write out the content
AotBundle aotBundle = creator.create(targetTypeSpec);
@ -134,22 +142,25 @@ public class RepositoryContributor { @@ -134,22 +142,25 @@ public class RepositoryContributor {
-------------------
""".formatted(aotBundle.repositoryJsonFileName(), repositoryJson));
JavaFile javaFile = JavaFile.builder(creator.packageName(), targetTypeSpec.build()).build();
logger.trace("""
------ AOT Generated Repository: %s ------
%s
-------------------
""".formatted(null, aotBundle.javaFile()));
""".formatted(null, javaFile));
}
generationContext.getGeneratedFiles().addResourceFile(aotBundle.repositoryJsonFileName(), repositoryJson);
// generate native runtime hints - needed cause we're using the repository proxy
generationContext.getRuntimeHints().reflection().registerType(aotBundle.generatedRepositoryTypeName(),
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_PUBLIC_METHODS);
});
// generate native runtime hints
// make sure to capture the target file name
this.contributedTypeName = GeneratedTypeReference.of(generatedClass.getName());
generationContext.getRuntimeHints().reflection().registerType(this.contributedTypeName,
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_PUBLIC_METHODS);
}
/**

23
src/main/java/org/springframework/data/repository/config/RepositoryRegistrationAotContribution.java

@ -23,6 +23,7 @@ import java.util.Optional; @@ -23,6 +23,7 @@ import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Predicate;
import java.util.function.Supplier;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@ -45,6 +46,7 @@ import org.springframework.data.aot.AotContext; @@ -45,6 +46,7 @@ import org.springframework.data.aot.AotContext;
import org.springframework.data.projection.EntityProjectionIntrospector;
import org.springframework.data.projection.TargetAware;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.aot.generate.AotRepositoryBeanDefinitionPropertiesDecorator;
import org.springframework.data.repository.aot.generate.RepositoryContributor;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.support.RepositoryFragment;
@ -118,8 +120,7 @@ public class RepositoryRegistrationAotContribution implements BeanRegistrationAo @@ -118,8 +120,7 @@ public class RepositoryRegistrationAotContribution implements BeanRegistrationAo
return null;
}
AotRepositoryContext repositoryContext = buildAotRepositoryContext(processor.getEnvironment(), repositoryBean,
repositoryMetadata);
AotRepositoryContext repositoryContext = buildAotRepositoryContext(processor.getEnvironment(), repositoryBean);
if (repositoryContext == null) {
return null;
@ -148,8 +149,7 @@ public class RepositoryRegistrationAotContribution implements BeanRegistrationAo @@ -148,8 +149,7 @@ public class RepositoryRegistrationAotContribution implements BeanRegistrationAo
return null;
}
AotRepositoryContext repositoryContext = buildAotRepositoryContext(aotProcessor.getEnvironment(), repositoryBean,
repositoryMetadata);
AotRepositoryContext repositoryContext = buildAotRepositoryContext(aotProcessor.getEnvironment(), repositoryBean);
if (repositoryContext == null) {
return null;
@ -178,8 +178,8 @@ public class RepositoryRegistrationAotContribution implements BeanRegistrationAo @@ -178,8 +178,8 @@ public class RepositoryRegistrationAotContribution implements BeanRegistrationAo
getRepositoryRegistrationAotProcessor().logTrace(message, arguments);
}
private static @Nullable AotRepositoryContext buildAotRepositoryContext(Environment environment, RegisteredBean bean,
RepositoryConfiguration<?> repositoryConfiguration) {
private static @Nullable AotRepositoryContext buildAotRepositoryContext(Environment environment,
RegisteredBean bean) {
RepositoryBeanDefinitionReader reader = new RepositoryBeanDefinitionReader(bean);
RepositoryConfiguration<?> configuration = reader.getConfiguration();
@ -240,16 +240,15 @@ public class RepositoryRegistrationAotContribution implements BeanRegistrationAo @@ -240,16 +240,15 @@ public class RepositoryRegistrationAotContribution implements BeanRegistrationAo
BeanRegistrationCode beanRegistrationCode, RootBeanDefinition beanDefinition,
Predicate<String> attributeFilter) {
if (repositoryContributor == null) { // no aot implementation -> go on as
Supplier<CodeBlock> inheritedProperties = () -> super.generateSetBeanDefinitionPropertiesCode(generationContext,
beanRegistrationCode, beanDefinition, attributeFilter);
return super.generateSetBeanDefinitionPropertiesCode(generationContext, beanRegistrationCode, beanDefinition,
attributeFilter);
if (repositoryContributor == null) { // no aot implementation -> go on as
return inheritedProperties.get();
}
AotRepositoryBeanDefinitionPropertiesDecorator decorator = new AotRepositoryBeanDefinitionPropertiesDecorator(
() -> super.generateSetBeanDefinitionPropertiesCode(generationContext, beanRegistrationCode, beanDefinition,
attributeFilter),
repositoryContributor);
inheritedProperties, repositoryContributor);
return decorator.decorate();
}

12
src/test/java/org/springframework/data/repository/config/AotRepositoryBeanDefinitionPropertiesDecoratorUnitTests.java → src/test/java/org/springframework/data/repository/aot/generate/AotRepositoryBeanDefinitionPropertiesDecoratorUnitTests.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2025-present the original author or authors.
* Copyright 2025 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.
@ -13,10 +13,10 @@ @@ -13,10 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.repository.config;
package org.springframework.data.repository.aot.generate;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import java.util.LinkedHashMap;
import java.util.List;
@ -28,16 +28,18 @@ import org.junit.jupiter.api.Test; @@ -28,16 +28,18 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.aot.generate.GeneratedTypeReference;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.core.ResolvableType;
import org.springframework.data.repository.aot.generate.RepositoryContributor;
import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
import org.springframework.data.util.Version;
import org.springframework.javapoet.ClassName;
import org.springframework.javapoet.CodeBlock;
/**
* Unit testa for {@link AotRepositoryBeanDefinitionPropertiesDecorator}.
*
* @author Christoph Strobl
*/
@ExtendWith(MockitoExtension.class)

13
src/test/java/org/springframework/data/repository/config/AotRepositoryConfigurationUnitTests.java → src/test/java/org/springframework/data/repository/aot/generate/AotRepositoryConfigurationUnitTests.java

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
/*
* Copyright 2025-present the original author or authors.
* Copyright 2025 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.
@ -13,22 +13,21 @@ @@ -13,22 +13,21 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.repository.config;
package org.springframework.data.repository.aot.generate;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import java.util.LinkedHashMap;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.springframework.aot.generate.GeneratedFiles.Kind;
import org.springframework.aot.hint.TypeReference;
import org.springframework.aot.test.generate.TestGenerationContext;
import org.springframework.core.ResolvableType;
import org.springframework.data.repository.aot.generate.AotRepositoryConstructorBuilder;
import org.springframework.data.repository.aot.generate.RepositoryContributor;
import org.springframework.data.repository.config.AotRepositoryContext;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.support.RepositoryComposition;
import org.springframework.data.repository.core.support.RepositoryFragment;

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

@ -15,9 +15,8 @@ @@ -15,9 +15,8 @@
*/
package org.springframework.data.repository.aot.generate;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;
import example.UserRepository.User;
@ -28,6 +27,8 @@ import javax.lang.model.element.Modifier; @@ -28,6 +27,8 @@ import javax.lang.model.element.Modifier;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.aot.generate.Generated;
import org.springframework.aot.hint.TypeReference;
import org.springframework.core.ResolvableType;
import org.springframework.data.geo.Metric;
@ -40,6 +41,7 @@ import org.springframework.data.repository.core.support.AnnotationRepositoryMeta @@ -40,6 +41,7 @@ import org.springframework.data.repository.core.support.AnnotationRepositoryMeta
import org.springframework.data.repository.core.support.RepositoryFragment;
import org.springframework.data.repository.query.QueryMethod;
import org.springframework.javapoet.ClassName;
import org.springframework.javapoet.JavaFile;
import org.springframework.javapoet.MethodSpec;
import org.springframework.javapoet.TypeSpec;
import org.springframework.stereotype.Repository;
@ -66,9 +68,9 @@ class AotRepositoryCreatorUnitTests { @@ -66,9 +68,9 @@ class AotRepositoryCreatorUnitTests {
AotRepositoryCreator repositoryCreator = AotRepositoryCreator.forRepository(repositoryInformation, "Commons",
new SpelAwareProxyProjectionFactory());
assertThat(repositoryCreator.create("%s.%s".formatted(UserRepository.class.getPackageName(), "UserRepositoryImpl"))
.generatedCode()).contains("package %s;".formatted(UserRepository.class.getPackageName())) // same package as
// source repo
// same package as source repo
assertThat(generate(repositoryCreator)).contains("package %s;".formatted(UserRepository.class.getPackageName()))
.contains("@Generated") // marked as generated source
.contains("public class %sImpl".formatted(UserRepository.class.getSimpleName())) // target name
.contains("public UserRepositoryImpl"); // default constructor if not arguments to wire
@ -84,9 +86,11 @@ class AotRepositoryCreatorUnitTests { @@ -84,9 +86,11 @@ class AotRepositoryCreatorUnitTests {
ctor.addParameter("param2", String.class);
ctor.addParameter("ctorScoped", ResolvableType.forType(Object.class), false);
});
assertThat(repositoryCreator
.create(TypeSpec.classBuilder(ClassName.get(UserRepository.class.getPackageName(), "UserRepositoryImpl")))
.generatedCode()) //
TypeSpec.Builder builder = TypeSpec
.classBuilder(ClassName.get(UserRepository.class.getPackageName(), "UserRepositoryImpl"));
assertThat(generate(builder, repositoryCreator)) //
.contains("private final Metric param1;") //
.contains("private final String param2;") //
.doesNotContain("private final Object ctorScoped;") //
@ -106,7 +110,7 @@ class AotRepositoryCreatorUnitTests { @@ -106,7 +110,7 @@ class AotRepositoryCreatorUnitTests {
code.addStatement("throw new $T($S)", IllegalStateException.class, "initialization error");
});
});
assertThat(repositoryCreator.create().generatedCode()).containsIgnoringWhitespaces(
assertThat(generate(repositoryCreator)).containsIgnoringWhitespaces(
"UserRepositoryImpl() { throw new IllegalStateException(\"initialization error\"); }");
}
@ -130,7 +134,7 @@ class AotRepositoryCreatorUnitTests { @@ -130,7 +134,7 @@ class AotRepositoryCreatorUnitTests {
});
});
assertThat(repositoryCreator.create().generatedCode()) //
assertThat(generate(repositoryCreator)) //
.contains("@Repository") //
.contains("private static Float f;") //
.contains("public Double d;") //
@ -147,7 +151,7 @@ class AotRepositoryCreatorUnitTests { @@ -147,7 +151,7 @@ class AotRepositoryCreatorUnitTests {
AotRepositoryCreator repositoryCreator = AotRepositoryCreator.forRepository(repositoryInformation, "Commons",
new SpelAwareProxyProjectionFactory());
repositoryCreator.resolveQueryMethods((method) -> {
repositoryCreator.contributeMethods((method) -> {
return new MethodContributor<>(mock(QueryMethod.class), null) {
@ -163,7 +167,7 @@ class AotRepositoryCreatorUnitTests { @@ -163,7 +167,7 @@ class AotRepositoryCreatorUnitTests {
};
});
assertThat(repositoryCreator.create().generatedCode()) //
assertThat(generate(repositoryCreator)) //
.containsIgnoringWhitespaces("void oops() { }");
}
@ -174,9 +178,10 @@ class AotRepositoryCreatorUnitTests { @@ -174,9 +178,10 @@ class AotRepositoryCreatorUnitTests {
AnnotationRepositoryMetadata.getMetadata(QuerydslUserRepository.class), CrudRepository.class,
List.of(RepositoryFragment.structural(QuerydslPredicateExecutor.class, DummyQuerydslPredicateExecutor.class)));
AotRepositoryCreator builder = AotRepositoryCreator
.forRepository(repositoryInformation, "Commons", new SpelAwareProxyProjectionFactory()).resolveQueryMethods();
AotRepositoryCreator.AotBundle bundle = builder.create();
AotRepositoryCreator creator = AotRepositoryCreator
.forRepository(repositoryInformation, "Commons", new SpelAwareProxyProjectionFactory());
creator.contributeMethods(method -> null);
AotRepositoryCreator.AotBundle bundle = doCreate(creator);
AotRepositoryMethod method = bundle.metadata().get().methods().stream().filter(it -> it.name().equals("findBy"))
.findFirst().get();
@ -199,7 +204,7 @@ class AotRepositoryCreatorUnitTests { @@ -199,7 +204,7 @@ class AotRepositoryCreatorUnitTests {
TypeReference targetType = TypeReference.of("%s__AotPostfix".formatted(UserRepository.class.getCanonicalName()));
assertThat(repositoryCreator.create(targetType.getSimpleName()).generatedCode()) //
assertThat(generate(ClassName.get(repositoryCreator.packageName(), targetType.getSimpleName()), repositoryCreator)) //
.contains("class %s".formatted(targetType.getSimpleName())) //
.contains("public %s(Metric param1, String param2, Object ctorScoped)".formatted(targetType.getSimpleName()));
}
@ -217,12 +222,43 @@ class AotRepositoryCreatorUnitTests { @@ -217,12 +222,43 @@ class AotRepositoryCreatorUnitTests {
TypeReference targetType = TypeReference.of("%s__AotPostfix".formatted(UserRepository.class.getCanonicalName()));
assertThat(repositoryCreator.create(targetType.getSimpleName()).generatedCode()) //
assertThat(generate(ClassName.get(repositoryCreator.packageName(), targetType.getSimpleName()), repositoryCreator)) //
.contains("class %s".formatted(targetType.getSimpleName())) //
.contains(
"public %s(List<Metric> param1, String param2, Object ctorScoped)".formatted(targetType.getSimpleName()));
}
private AotRepositoryCreator.AotBundle doCreate(AotRepositoryCreator creator) {
return creator.create(getTypeSpecBuilder(creator));
}
private String generate(AotRepositoryCreator creator) {
TypeSpec.Builder builder = getTypeSpecBuilder(creator);
return generate(builder, creator);
}
private String generate(ClassName className, AotRepositoryCreator creator) {
TypeSpec.Builder builder = getTypeSpecBuilder(className);
return generate(builder, creator);
}
private String generate(TypeSpec.Builder builder, AotRepositoryCreator creator) {
creator.create(builder);
return JavaFile.builder(creator.packageName(), builder.build()).build().toString();
}
private static TypeSpec.Builder getTypeSpecBuilder(AotRepositoryCreator creator) {
return getTypeSpecBuilder(creator.getClassName());
}
private static TypeSpec.Builder getTypeSpecBuilder(ClassName className) {
return TypeSpec.classBuilder(className).addAnnotation(Generated.class);
}
interface UserRepository extends org.springframework.data.repository.Repository<User, String> {
String someMethod();

12
src/test/java/org/springframework/data/repository/aot/generate/RepositoryContributorUnitTests.java

@ -108,15 +108,9 @@ class RepositoryContributorUnitTests { @@ -108,15 +108,9 @@ class RepositoryContributorUnitTests {
return MethodContributor
.forQueryMethod(
new QueryMethod(method, getRepositoryInformation(), getProjectionFactory(), DefaultParameters::new))
.withMetadata(new QueryMetadata() {
@Override
public Map<String, Object> serialize() {
return Map.of("filter", "FILTER(%s > $1)".formatted(method.getName()), "project",
Arrays.stream(method.getParameters()).map(Parameter::getName).toList());
}
}).contribute(context -> {
.withMetadata(() -> Map.of("filter", "FILTER(%s > $1)".formatted(method.getName()), "project",
Arrays.stream(method.getParameters()).map(Parameter::getName).toList()))
.contribute(context -> {
CodeBlock.Builder builder = CodeBlock.builder();
if (!ClassUtils.isVoidType(method.getReturnType())) {

Loading…
Cancel
Save