Skip to content

Commit dc5483a

Browse files
christophstroblmp911de
authored andcommitted
Use generated classname for writing aot repository content.
This change makes sure register AOT repository code with a generated typename. This is necessary to allow recreation for repository code with different configuration/context settings during tests. Closes #3339 Original pull request: #3345
1 parent 370cb37 commit dc5483a

File tree

6 files changed

+140
-86
lines changed

6 files changed

+140
-86
lines changed

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

Lines changed: 43 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -29,17 +29,16 @@
2929
import org.apache.commons.logging.Log;
3030
import org.apache.commons.logging.LogFactory;
3131
import org.jspecify.annotations.Nullable;
32-
33-
import org.springframework.aot.generate.ClassNameGenerator;
3432
import org.springframework.aot.generate.Generated;
33+
import org.springframework.aot.generate.GeneratedTypeReference;
34+
import org.springframework.aot.hint.TypeReference;
3535
import org.springframework.data.projection.ProjectionFactory;
3636
import org.springframework.data.repository.aot.generate.AotRepositoryFragmentMetadata.ConstructorArgument;
3737
import org.springframework.data.repository.core.RepositoryInformation;
3838
import org.springframework.data.repository.core.support.RepositoryComposition;
3939
import org.springframework.data.repository.core.support.RepositoryFragment;
4040
import org.springframework.data.repository.query.QueryMethod;
4141
import org.springframework.javapoet.ClassName;
42-
import org.springframework.javapoet.FieldSpec;
4342
import org.springframework.javapoet.JavaFile;
4443
import org.springframework.javapoet.MethodSpec;
4544
import org.springframework.javapoet.TypeName;
@@ -64,6 +63,8 @@ class AotRepositoryBuilder {
6463
private @Nullable Consumer<AotRepositoryConstructorBuilder> constructorCustomizer;
6564
private @Nullable MethodContributorFactory methodContributorFactory;
6665
private Consumer<AotRepositoryClassBuilder> classCustomizer;
66+
private @Nullable TypeReference targetClassName;
67+
private RepositoryConstructorBuilder constructorBuilder;
6768

6869
private AotRepositoryBuilder(RepositoryInformation repositoryInformation, String moduleName,
6970
ProjectionFactory projectionFactory) {
@@ -72,13 +73,9 @@ private AotRepositoryBuilder(RepositoryInformation repositoryInformation, String
7273
this.moduleName = moduleName;
7374
this.projectionFactory = projectionFactory;
7475

75-
this.generationMetadata = new AotRepositoryFragmentMetadata(className());
76-
this.generationMetadata.addField(FieldSpec
77-
.builder(TypeName.get(Log.class), "logger", Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
78-
.initializer("$T.getLog($T.class)", TypeName.get(LogFactory.class), this.generationMetadata.getTargetTypeName())
79-
.build());
80-
76+
this.generationMetadata = new AotRepositoryFragmentMetadata();
8177
this.classCustomizer = (builder) -> {};
78+
this.constructorBuilder = new RepositoryConstructorBuilder(generationMetadata);
8279
}
8380

8481
/**
@@ -131,15 +128,24 @@ public AotRepositoryBuilder withQueryMethodContributor(MethodContributorFactory
131128
return this;
132129
}
133130

134-
public AotBundle build() {
131+
public AotRepositoryBuilder prepare(@Nullable ClassName targetClassName) {
132+
if (targetClassName == null) {
133+
withTargetClassName(null);
134+
} else {
135+
withTargetClassName(GeneratedTypeReference.of(targetClassName));
136+
}
137+
if (constructorCustomizer != null) {
138+
constructorCustomizer.accept(constructorBuilder);
139+
}
140+
return this;
141+
}
142+
143+
public AotBundle build(TypeSpec.Builder builder) {
135144

136145
List<AotRepositoryMethod> methodMetadata = new ArrayList<>();
137146
RepositoryComposition repositoryComposition = repositoryInformation.getRepositoryComposition();
138147

139-
// start creating the type
140-
TypeSpec.Builder builder = TypeSpec.classBuilder(this.generationMetadata.getTargetTypeName()) //
141-
.addModifiers(Modifier.PUBLIC) //
142-
.addAnnotation(Generated.class) //
148+
builder.addModifiers(Modifier.PUBLIC) //
143149
.addJavadoc("AOT generated $L repository implementation for {@link $T}.\n", moduleName,
144150
repositoryInformation.getRepositoryInterface());
145151

@@ -177,15 +183,31 @@ public AotBundle build() {
177183
return new AotBundle(javaFile, metadata);
178184
}
179185

180-
private MethodSpec buildConstructor() {
186+
public AotBundle build() {
187+
188+
ClassName className = ClassName
189+
.bestGuess((targetClassName != null ? targetClassName : intendedTargetClassName()).getCanonicalName());
190+
return build(TypeSpec.classBuilder(className).addAnnotation(Generated.class));
191+
}
181192

182-
RepositoryConstructorBuilder constructorBuilder = new RepositoryConstructorBuilder(
183-
generationMetadata);
193+
public TypeReference intendedTargetClassName() {
194+
return TypeReference.of("%s.%s".formatted(packageName(), typeName()));
195+
}
184196

185-
if (constructorCustomizer != null) {
186-
constructorCustomizer.accept(constructorBuilder);
197+
public @Nullable TypeReference actualTargetClassName() {
198+
199+
if (targetClassName == null) {
200+
return null;
187201
}
202+
return targetClassName;
203+
}
188204

205+
AotRepositoryBuilder withTargetClassName(@Nullable TypeReference targetClassName) {
206+
this.targetClassName = targetClassName;
207+
return this;
208+
}
209+
210+
private MethodSpec buildConstructor() {
189211
return constructorBuilder.buildConstructor();
190212
}
191213

@@ -252,15 +274,11 @@ public AotRepositoryFragmentMetadata getGenerationMetadata() {
252274
return generationMetadata;
253275
}
254276

255-
private ClassName className() {
256-
return new ClassNameGenerator(ClassName.get(packageName(), typeName())).generateClassName("Aot", null);
257-
}
258-
259-
private String packageName() {
277+
public String packageName() {
260278
return repositoryInformation.getRepositoryInterface().getPackageName();
261279
}
262280

263-
private String typeName() {
281+
public String typeName() {
264282
return "%sImpl".formatted(repositoryInformation.getRepositoryInterface().getSimpleName());
265283
}
266284

@@ -280,7 +298,6 @@ public ProjectionFactory getProjectionFactory() {
280298
return projectionFactory;
281299
}
282300

283-
284301
/**
285302
* Customizer interface to customize the AOT repository fragment constructor through
286303
* {@link AotRepositoryConstructorBuilder}.

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

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,10 @@
3737
*/
3838
public class AotRepositoryFragmentMetadata {
3939

40-
private final ClassName className;
4140
private final Map<String, FieldSpec> fields = new HashMap<>(3);
4241
private final Map<String, ConstructorArgument> constructorArguments = new LinkedHashMap<>(3);
4342

44-
public AotRepositoryFragmentMetadata(ClassName className) {
45-
this.className = className;
43+
public AotRepositoryFragmentMetadata() {
4644
}
4745

4846
/**
@@ -65,10 +63,6 @@ public String fieldNameOf(Class<?> type) {
6563
return null;
6664
}
6765

68-
public ClassName getTargetTypeName() {
69-
return className;
70-
}
71-
7266
/**
7367
* Add a field to the repository fragment.
7468
*

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

Lines changed: 51 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,18 @@
2020
import org.apache.commons.logging.Log;
2121
import org.apache.commons.logging.LogFactory;
2222
import org.jspecify.annotations.Nullable;
23-
23+
import org.springframework.aot.generate.GeneratedClass;
24+
import org.springframework.aot.generate.GeneratedTypeReference;
2425
import org.springframework.aot.generate.GenerationContext;
2526
import org.springframework.aot.hint.MemberCategory;
2627
import org.springframework.aot.hint.TypeReference;
2728
import org.springframework.data.projection.ProjectionFactory;
2829
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
30+
import org.springframework.data.repository.aot.generate.AotRepositoryBuilder.AotBundle;
2931
import org.springframework.data.repository.config.AotRepositoryContext;
3032
import org.springframework.data.repository.core.RepositoryInformation;
3133
import org.springframework.data.repository.query.QueryMethod;
32-
import org.springframework.javapoet.JavaFile;
34+
import org.springframework.javapoet.ClassName;
3335
import org.springframework.javapoet.TypeName;
3436

3537
/**
@@ -42,16 +44,21 @@
4244
public class RepositoryContributor {
4345

4446
private static final Log logger = LogFactory.getLog(RepositoryContributor.class);
47+
private static final String FEATURE_NAME = "AotRepository";
4548

46-
private final AotRepositoryBuilder builder;
49+
private AotRepositoryBuilder builder;
50+
private final AotRepositoryContext repositoryContext;
51+
private @Nullable TypeReference contributedTypeName;
4752

4853
/**
4954
* Create a new {@code RepositoryContributor} for the given {@link AotRepositoryContext}.
5055
*
5156
* @param repositoryContext
5257
*/
5358
public RepositoryContributor(AotRepositoryContext repositoryContext) {
54-
this.builder = AotRepositoryBuilder.forRepository(repositoryContext.getRepositoryInformation(),
59+
60+
this.repositoryContext = repositoryContext;
61+
builder = AotRepositoryBuilder.forRepository(repositoryContext.getRepositoryInformation(),
5562
repositoryContext.getModuleName(), createProjectionFactory());
5663
}
5764

@@ -77,8 +84,8 @@ protected RepositoryInformation getRepositoryInformation() {
7784
return builder.getRepositoryInformation();
7885
}
7986

80-
public String getContributedTypeName() {
81-
return builder.getGenerationMetadata().getTargetTypeName().toString();
87+
public @Nullable TypeReference getContributedTypeName() {
88+
return this.contributedTypeName;
8289
}
8390

8491
public java.util.Map<String, TypeName> requiredArgs() {
@@ -87,44 +94,47 @@ public java.util.Map<String, TypeName> requiredArgs() {
8794

8895
public void contribute(GenerationContext generationContext) {
8996

90-
AotRepositoryBuilder.AotBundle aotBundle = builder.withClassCustomizer(this::customizeClass) //
97+
builder.withClassCustomizer(this::customizeClass) //
9198
.withConstructorCustomizer(this::customizeConstructor) //
92-
.withQueryMethodContributor(this::contributeQueryMethod) //
93-
.build();
94-
95-
Class<?> repositoryInterface = getRepositoryInformation().getRepositoryInterface();
96-
String repositoryJsonFileName = getRepositoryJsonFileName(repositoryInterface);
97-
98-
JavaFile javaFile = aotBundle.javaFile();
99-
String typeName = "%s.%s".formatted(javaFile.packageName(), javaFile.typeSpec().name());
100-
String repositoryJson;
101-
102-
try {
103-
repositoryJson = aotBundle.metadata().toJson().toString(2);
104-
} catch (JSONException e) {
105-
throw new RuntimeException(e);
106-
}
107-
108-
if (logger.isTraceEnabled()) {
109-
logger.trace("""
110-
------ AOT Repository.json: %s ------
111-
%s
112-
-------------------
113-
""".formatted(repositoryJsonFileName, repositoryJson));
114-
115-
logger.trace("""
116-
------ AOT Generated Repository: %s ------
117-
%s
118-
-------------------
119-
""".formatted(typeName, javaFile));
120-
}
121-
122-
// generate the files
123-
generationContext.getGeneratedFiles().addSourceFile(javaFile);
124-
generationContext.getGeneratedFiles().addResourceFile(repositoryJsonFileName, repositoryJson);
99+
.withQueryMethodContributor(this::contributeQueryMethod); //
100+
101+
GeneratedClass generatedClass = generationContext.getGeneratedClasses().getOrAddForFeatureComponent(FEATURE_NAME,
102+
ClassName.bestGuess(builder.intendedTargetClassName().getCanonicalName()), targetTypeSpec -> {
103+
104+
AotBundle aotBundle = builder.build(targetTypeSpec);
105+
{
106+
Class<?> repositoryInterface = getRepositoryInformation().getRepositoryInterface();
107+
String repositoryJsonFileName = getRepositoryJsonFileName(repositoryInterface);
108+
String repositoryJson;
109+
try {
110+
repositoryJson = aotBundle.metadata().toJson().toString(2);
111+
} catch (JSONException e) {
112+
throw new RuntimeException(e);
113+
}
114+
115+
if (logger.isTraceEnabled()) {
116+
logger.trace("""
117+
------ AOT Repository.json: %s ------
118+
%s
119+
-------------------
120+
""".formatted(repositoryJsonFileName, repositoryJson));
121+
122+
logger.trace("""
123+
------ AOT Generated Repository: %s ------
124+
%s
125+
-------------------
126+
""".formatted(null, aotBundle.javaFile()));
127+
}
128+
129+
generationContext.getGeneratedFiles().addResourceFile(repositoryJsonFileName, repositoryJson);
130+
}
131+
});
132+
133+
builder.prepare(generatedClass.getName()); // initialize ctor argument resolution and set type name to target
134+
this.contributedTypeName = GeneratedTypeReference.of(generatedClass.getName());
125135

126136
// generate native runtime hints - needed cause we're using the repository proxy
127-
generationContext.getRuntimeHints().reflection().registerType(TypeReference.of(typeName),
137+
generationContext.getRuntimeHints().reflection().registerType(this.contributedTypeName,
128138
MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_PUBLIC_METHODS);
129139
}
130140

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport;
2525
import org.springframework.javapoet.CodeBlock;
2626
import org.springframework.javapoet.TypeName;
27+
import org.springframework.util.Assert;
2728
import org.springframework.util.StringUtils;
2829

2930
/**
@@ -51,6 +52,8 @@ public AotRepositoryBeanDefinitionPropertiesDecorator(Supplier<CodeBlock> inheri
5152
*/
5253
public CodeBlock decorate() {
5354

55+
Assert.notNull(repositoryContributor.getContributedTypeName(), "contributed type name must not be null");
56+
5457
CodeBlock.Builder builder = CodeBlock.builder();
5558
// bring in properties as usual
5659
builder.add(inheritedProperties.get());
@@ -78,7 +81,7 @@ public CodeBlock decorate() {
7881
}
7982

8083
builder.addStatement("return RepositoryComposition.RepositoryFragments.just(new $L($L))",
81-
repositoryContributor.getContributedTypeName(),
84+
repositoryContributor.getContributedTypeName().getCanonicalName(),
8285
StringUtils.collectionToDelimitedString(repositoryContributor.requiredArgs().keySet(), ", "));
8386
builder.unindent();
8487
builder.add("}\n");

0 commit comments

Comments
 (0)