Browse Source

Polishing.

Capture class name from within the builder callback. Simplify name handling.

See #3339
Original pull request: #3345
pull/3352/head
Mark Paluch 4 months ago
parent
commit
f43e37213c
No known key found for this signature in database
GPG Key ID: 55BC6374BAA9D973
  1. 48
      src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryBuilder.java
  2. 66
      src/main/java/org/springframework/data/repository/aot/generate/RepositoryContributor.java
  3. 2
      src/main/java/org/springframework/data/repository/config/AotRepositoryBeanDefinitionPropertiesDecorator.java
  4. 7
      src/test/java/org/springframework/data/repository/aot/generate/AotRepositoryBuilderUnitTests.java

48
src/main/java/org/springframework/data/repository/aot/generate/AotRepositoryBuilder.java

@ -29,9 +29,8 @@ import javax.lang.model.element.Modifier; @@ -29,9 +29,8 @@ 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.data.projection.ProjectionFactory;
import org.springframework.data.repository.aot.generate.AotRepositoryFragmentMetadata.ConstructorArgument;
import org.springframework.data.repository.core.RepositoryInformation;
@ -62,9 +61,9 @@ class AotRepositoryBuilder { @@ -62,9 +61,9 @@ class AotRepositoryBuilder {
private @Nullable Consumer<AotRepositoryConstructorBuilder> constructorCustomizer;
private @Nullable MethodContributorFactory methodContributorFactory;
private @Nullable String targetClassName;
private Consumer<AotRepositoryClassBuilder> classCustomizer;
private @Nullable TypeReference targetClassName;
private RepositoryConstructorBuilder constructorBuilder;
private final RepositoryConstructorBuilder constructorBuilder;
private AotRepositoryBuilder(RepositoryInformation repositoryInformation, String moduleName,
ProjectionFactory projectionFactory) {
@ -128,15 +127,15 @@ class AotRepositoryBuilder { @@ -128,15 +127,15 @@ class AotRepositoryBuilder {
return this;
}
public AotRepositoryBuilder prepare(@Nullable ClassName targetClassName) {
if (targetClassName == null) {
withTargetClassName(null);
} else {
withTargetClassName(GeneratedTypeReference.of(targetClassName));
}
if (constructorCustomizer != null) {
constructorCustomizer.accept(constructorBuilder);
}
/**
* Configure the {@link Class#getSimpleName() simple class name} of the generated repository implementation.
*
* @param className the class name to use for the generated repository implementation. Defaults to the simple
* {@link RepositoryInformation#getRepositoryInterface()} class name suffixed with {@code Impl}
* @return {@code this}.
*/
public AotRepositoryBuilder withClassName(@Nullable String className) {
this.targetClassName = className;
return this;
}
@ -184,30 +183,19 @@ class AotRepositoryBuilder { @@ -184,30 +183,19 @@ class AotRepositoryBuilder {
}
public AotBundle build() {
ClassName className = ClassName
.bestGuess((targetClassName != null ? targetClassName : intendedTargetClassName()).getCanonicalName());
return build(TypeSpec.classBuilder(className).addAnnotation(Generated.class));
return build(TypeSpec.classBuilder(getClassName()).addAnnotation(Generated.class));
}
public TypeReference intendedTargetClassName() {
return TypeReference.of("%s.%s".formatted(packageName(), typeName()));
public ClassName getClassName() {
return ClassName.get(packageName(), targetClassName != null ? targetClassName : typeName());
}
public @Nullable TypeReference actualTargetClassName() {
private MethodSpec buildConstructor() {
if (targetClassName == null) {
return null;
if (constructorCustomizer != null) {
constructorCustomizer.accept(constructorBuilder);
}
return targetClassName;
}
AotRepositoryBuilder withTargetClassName(@Nullable TypeReference targetClassName) {
this.targetClassName = targetClassName;
return this;
}
private MethodSpec buildConstructor() {
return constructorBuilder.buildConstructor();
}

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

@ -20,6 +20,7 @@ import java.lang.reflect.Method; @@ -20,6 +20,7 @@ import java.lang.reflect.Method;
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;
@ -31,7 +32,6 @@ import org.springframework.data.repository.aot.generate.AotRepositoryBuilder.Aot @@ -31,7 +32,6 @@ import org.springframework.data.repository.aot.generate.AotRepositoryBuilder.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.TypeName;
/**
@ -46,8 +46,7 @@ public class RepositoryContributor { @@ -46,8 +46,7 @@ public class RepositoryContributor {
private static final Log logger = LogFactory.getLog(RepositoryContributor.class);
private static final String FEATURE_NAME = "AotRepository";
private AotRepositoryBuilder builder;
private final AotRepositoryContext repositoryContext;
private final AotRepositoryBuilder builder;
private @Nullable TypeReference contributedTypeName;
/**
@ -57,7 +56,6 @@ public class RepositoryContributor { @@ -57,7 +56,6 @@ public class RepositoryContributor {
*/
public RepositoryContributor(AotRepositoryContext repositoryContext) {
this.repositoryContext = repositoryContext;
builder = AotRepositoryBuilder.forRepository(repositoryContext.getRepositoryInformation(),
repositoryContext.getModuleName(), createProjectionFactory());
}
@ -98,39 +96,45 @@ public class RepositoryContributor { @@ -98,39 +96,45 @@ public class RepositoryContributor {
.withConstructorCustomizer(this::customizeConstructor) //
.withQueryMethodContributor(this::contributeQueryMethod); //
// TODO: temporary fix until we have a better representation of constructor arguments
// decouple the description of arguments from the actual code used in the constructor initialization, super calls,
// etc.
RepositoryConstructorBuilder constructorBuilder = new RepositoryConstructorBuilder(builder.getGenerationMetadata());
customizeConstructor(constructorBuilder);
GeneratedClass generatedClass = generationContext.getGeneratedClasses().getOrAddForFeatureComponent(FEATURE_NAME,
ClassName.bestGuess(builder.intendedTargetClassName().getCanonicalName()), targetTypeSpec -> {
builder.getClassName(), targetTypeSpec -> {
// capture the actual type name early on so that we can use it in the constructor.
builder.withClassName(targetTypeSpec.build().name());
AotBundle aotBundle = builder.build(targetTypeSpec);
{
Class<?> repositoryInterface = getRepositoryInformation().getRepositoryInterface();
String repositoryJsonFileName = getRepositoryJsonFileName(repositoryInterface);
String repositoryJson;
try {
repositoryJson = aotBundle.metadata().toJson().toString(2);
} catch (JSONException e) {
throw new RuntimeException(e);
}
if (logger.isTraceEnabled()) {
logger.trace("""
------ AOT Repository.json: %s ------
%s
-------------------
""".formatted(repositoryJsonFileName, repositoryJson));
logger.trace("""
------ AOT Generated Repository: %s ------
%s
-------------------
""".formatted(null, aotBundle.javaFile()));
}
generationContext.getGeneratedFiles().addResourceFile(repositoryJsonFileName, repositoryJson);
Class<?> repositoryInterface = getRepositoryInformation().getRepositoryInterface();
String repositoryJsonFileName = getRepositoryJsonFileName(repositoryInterface);
String repositoryJson;
try {
repositoryJson = aotBundle.metadata().toJson().toString(2);
} catch (JSONException e) {
throw new RuntimeException(e);
}
if (logger.isTraceEnabled()) {
logger.trace("""
------ AOT Repository.json: %s ------
%s
-------------------
""".formatted(repositoryJsonFileName, repositoryJson));
logger.trace("""
------ AOT Generated Repository: %s ------
%s
-------------------
""".formatted(null, aotBundle.javaFile()));
}
generationContext.getGeneratedFiles().addResourceFile(repositoryJsonFileName, repositoryJson);
});
builder.prepare(generatedClass.getName()); // initialize ctor argument resolution and set type name to target
this.contributedTypeName = GeneratedTypeReference.of(generatedClass.getName());
// generate native runtime hints - needed cause we're using the repository proxy

2
src/main/java/org/springframework/data/repository/config/AotRepositoryBeanDefinitionPropertiesDecorator.java

@ -52,7 +52,7 @@ class AotRepositoryBeanDefinitionPropertiesDecorator { @@ -52,7 +52,7 @@ class AotRepositoryBeanDefinitionPropertiesDecorator {
*/
public CodeBlock decorate() {
Assert.notNull(repositoryContributor.getContributedTypeName(), "contributed type name must not be null");
Assert.notNull(repositoryContributor.getContributedTypeName(), "Contributed type name must not be null");
CodeBlock.Builder builder = CodeBlock.builder();
// bring in properties as usual

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

@ -38,7 +38,6 @@ import org.springframework.data.repository.core.RepositoryInformation; @@ -38,7 +38,6 @@ import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.support.AnnotationRepositoryMetadata;
import org.springframework.data.repository.core.support.RepositoryFragment;
import org.springframework.data.repository.query.QueryMethod;
import org.springframework.javapoet.ClassName;
import org.springframework.javapoet.MethodSpec;
import org.springframework.javapoet.TypeName;
import org.springframework.stereotype.Repository;
@ -82,7 +81,7 @@ class AotRepositoryBuilderUnitTests { @@ -82,7 +81,7 @@ class AotRepositoryBuilderUnitTests {
ctor.addParameter("param2", String.class);
ctor.addParameter("ctorScoped", TypeName.get(Object.class), false);
});
assertThat(repoBuilder.prepare(null).build().javaFile().toString()) //
assertThat(repoBuilder.withClassName(null).build().javaFile().toString()) //
.contains("private final Metric param1;") //
.contains("private final String param2;") //
.doesNotContain("private final Object ctorScoped;") //
@ -102,7 +101,7 @@ class AotRepositoryBuilderUnitTests { @@ -102,7 +101,7 @@ class AotRepositoryBuilderUnitTests {
code.addStatement("throw new $T($S)", IllegalStateException.class, "initialization error");
});
});
repoBuilder.prepare(null);
repoBuilder.withClassName(null);
assertThat(repoBuilder.build().javaFile().toString()).containsIgnoringWhitespaces(
"UserRepositoryImpl() { throw new IllegalStateException(\"initialization error\"); }");
}
@ -196,7 +195,7 @@ class AotRepositoryBuilderUnitTests { @@ -196,7 +195,7 @@ class AotRepositoryBuilderUnitTests {
TypeReference targetType = TypeReference.of("%s__AotPostfix".formatted(UserRepository.class.getCanonicalName()));
assertThat(repoBuilder.prepare(ClassName.bestGuess(targetType.getCanonicalName())).build().javaFile().toString()) //
assertThat(repoBuilder.withClassName(targetType.getSimpleName()).build().javaFile().toString()) //
.contains("class %s".formatted(targetType.getSimpleName())) //
.contains("public %s(Metric param1, String param2, Object ctorScoped)".formatted(targetType.getSimpleName()));
}

Loading…
Cancel
Save