Skip to content

Commit 4bd33cb

Browse files
committed
Allow ApplicationContextAotGenerator to generated better class names
Update `ApplicationContextAotGenerator` so that it can generate class names based on a `target` class and using the ID of the application context. Prior to this commit, the generated class name was always `__.BeanFactoryRegistrations`. Closes gh-28565
1 parent 2433500 commit 4bd33cb

File tree

9 files changed

+99
-132
lines changed

9 files changed

+99
-132
lines changed

spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanFactoryInitializationCode.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import org.springframework.aot.generate.MethodGenerator;
2020
import org.springframework.aot.generate.MethodReference;
21+
import org.springframework.lang.Nullable;
2122

2223
/**
2324
* Interface that can be used to configure the code that will be generated to
@@ -34,6 +35,24 @@ public interface BeanFactoryInitializationCode {
3435
*/
3536
String BEAN_FACTORY_VARIABLE = "beanFactory";
3637

38+
/**
39+
* Return the target class for this bean factory or {@code null} if there is
40+
* no target.
41+
* @return the target
42+
*/
43+
@Nullable
44+
default Class<?> getTarget() {
45+
return null;
46+
}
47+
48+
/**
49+
* Return the ID of the bean factory or and empty string if no ID is avaialble.
50+
* @return the bean factory ID
51+
*/
52+
default String getId() {
53+
return "";
54+
}
55+
3756
/**
3857
* Return a {@link MethodGenerator} that can be used to add more methods to
3958
* the Initializing code.

spring-beans/src/main/java/org/springframework/beans/factory/aot/BeanRegistrationsAotContribution.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,9 @@ class BeanRegistrationsAotContribution
6161
public void applyTo(GenerationContext generationContext,
6262
BeanFactoryInitializationCode beanFactoryInitializationCode) {
6363

64-
ClassName className = generationContext.getClassNameGenerator()
65-
.generateClassName("BeanFactory", "Registrations");
64+
ClassName className = generationContext.getClassNameGenerator().generateClassName(
65+
beanFactoryInitializationCode.getTarget(),
66+
beanFactoryInitializationCode.getId() + "BeanFactoryRegistrations");
6667
BeanRegistrationsCodeGenerator codeGenerator = new BeanRegistrationsCodeGenerator(
6768
className);
6869
GeneratedMethod registerMethod = codeGenerator.getMethodGenerator()

spring-context/src/main/java/org/springframework/context/aot/ApplicationContextAotGenerator.java

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.springframework.context.support.GenericApplicationContext;
2424
import org.springframework.javapoet.ClassName;
2525
import org.springframework.javapoet.JavaFile;
26+
import org.springframework.lang.Nullable;
2627

2728
/**
2829
* Process an {@link ApplicationContext} and its {@link BeanFactory} to generate
@@ -48,10 +49,29 @@ public void generateApplicationContext(GenericApplicationContext applicationCont
4849
GenerationContext generationContext,
4950
ClassName generatedInitializerClassName) {
5051

52+
generateApplicationContext(applicationContext, null, generationContext,
53+
generatedInitializerClassName);
54+
}
55+
56+
/**
57+
* Refresh the specified {@link GenericApplicationContext} and generate the
58+
* necessary code to restore the state of its {@link BeanFactory}, using the
59+
* specified {@link GenerationContext}.
60+
* @param applicationContext the application context to handle
61+
* @param target the target class for the generated initializer
62+
* @param generationContext the generation context to use
63+
* @param generatedInitializerClassName the class name to use for the
64+
* generated application context initializer
65+
*/
66+
public void generateApplicationContext(GenericApplicationContext applicationContext,
67+
@Nullable Class<?> target, GenerationContext generationContext,
68+
ClassName generatedInitializerClassName) {
69+
5170
applicationContext.refreshForAotProcessing();
5271
DefaultListableBeanFactory beanFactory = applicationContext
5372
.getDefaultListableBeanFactory();
54-
ApplicationContextInitializationCodeGenerator codeGenerator = new ApplicationContextInitializationCodeGenerator();
73+
ApplicationContextInitializationCodeGenerator codeGenerator = new ApplicationContextInitializationCodeGenerator(
74+
target, applicationContext.getId());
5575
new BeanFactoryInitializationAotContributions(beanFactory).applyTo(generationContext,
5676
codeGenerator);
5777
JavaFile javaFile = codeGenerator.generateJavaFile(generatedInitializerClassName);

spring-context/src/main/java/org/springframework/context/aot/ApplicationContextInitializationCodeGenerator.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import org.springframework.javapoet.MethodSpec;
3636
import org.springframework.javapoet.ParameterizedTypeName;
3737
import org.springframework.javapoet.TypeSpec;
38+
import org.springframework.util.StringUtils;
3839

3940
/**
4041
* Internal code generator to create the application context initializer.
@@ -48,11 +49,31 @@ class ApplicationContextInitializationCodeGenerator
4849
private static final String APPLICATION_CONTEXT_VARIABLE = "applicationContext";
4950

5051

52+
private final Class<?> target;
53+
54+
private final String id;
55+
5156
private final GeneratedMethods generatedMethods = new GeneratedMethods();
5257

5358
private final List<MethodReference> initializers = new ArrayList<>();
5459

5560

61+
ApplicationContextInitializationCodeGenerator(Class<?> target, String id) {
62+
this.target=target;
63+
this.id = (!StringUtils.hasText(id)) ? "" : id;
64+
}
65+
66+
67+
@Override
68+
public Class<?> getTarget() {
69+
return this.target;
70+
}
71+
72+
@Override
73+
public String getId() {
74+
return this.id;
75+
}
76+
5677
@Override
5778
public MethodGenerator getMethodGenerator() {
5879
return this.generatedMethods;

spring-core/src/main/java/org/springframework/aot/generate/ClassGenerator.java

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -43,18 +43,6 @@ public interface ClassGenerator {
4343
GeneratedClass getOrGenerateClass(JavaFileGenerator javaFileGenerator,
4444
Class<?> target, String featureName);
4545

46-
/**
47-
* Get or generate a new {@link GeneratedClass} for a given java file
48-
* generator, target and feature name.
49-
* @param javaFileGenerator the java file generator
50-
* @param target the target of the newly generated class
51-
* @param featureName the name of the feature that the generated class
52-
* supports
53-
* @return a {@link GeneratedClass} instance
54-
*/
55-
GeneratedClass getOrGenerateClass(JavaFileGenerator javaFileGenerator, String target,
56-
String featureName);
57-
5846

5947
/**
6048
* Strategy used to generate the java file for the generated class.

spring-core/src/main/java/org/springframework/aot/generate/ClassNameGenerator.java

Lines changed: 17 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.util.concurrent.atomic.AtomicInteger;
2222

2323
import org.springframework.javapoet.ClassName;
24+
import org.springframework.lang.Nullable;
2425
import org.springframework.util.Assert;
2526
import org.springframework.util.ClassUtils;
2627
import org.springframework.util.StringUtils;
@@ -38,62 +39,47 @@ public final class ClassNameGenerator {
3839

3940
private static final String SEPARATOR = "__";
4041

41-
private static final String AOT_PACKAGE = "__";
42+
private static final String AOT_PACKAGE = "__.";
4243

44+
private static final String AOT_FEATURE = "Aot";
4345

4446
private final Map<String, AtomicInteger> sequenceGenerator = new ConcurrentHashMap<>();
4547

4648

4749
/**
4850
* Generate a new class name for the given {@code target} /
4951
* {@code featureName} combination.
50-
* @param target the target of the newly generated class
52+
* @param target the target of the newly generated class or {@code null} if
53+
* there is not target.
5154
* @param featureName the name of the feature that the generated class
5255
* supports
5356
* @return a unique generated class name
5457
*/
55-
public ClassName generateClassName(Class<?> target, String featureName) {
56-
Assert.notNull(target, "'target' must not be null");
57-
String rootName = target.getName().replace("$", "_");
58-
return generateSequencedClassName(rootName, featureName);
59-
}
60-
61-
/**
62-
* Generate a new class name for the given {@code name} /
63-
* {@code featureName} combination.
64-
* @param target the target of the newly generated class. When possible,
65-
* this should be a class name
66-
* @param featureName the name of the feature that the generated class
67-
* supports
68-
* @return a unique generated class name
69-
*/
70-
public ClassName generateClassName(String target, String featureName) {
71-
Assert.hasLength(target, "'target' must not be empty");
72-
target = clean(target);
73-
String rootName = AOT_PACKAGE + "." + ((!target.isEmpty()) ? target : "Aot");
74-
return generateSequencedClassName(rootName, featureName);
58+
public ClassName generateClassName(@Nullable Class<?> target, String featureName) {
59+
Assert.hasLength(featureName, "'featureName' must not be empty");
60+
featureName = clean(featureName);
61+
if(target != null) {
62+
return generateSequencedClassName(target.getName().replace("$", "_") + SEPARATOR + StringUtils.capitalize(featureName));
63+
}
64+
return generateSequencedClassName(AOT_PACKAGE+ featureName);
7565
}
7666

7767
private String clean(String name) {
78-
StringBuilder rootName = new StringBuilder();
68+
StringBuilder clean = new StringBuilder();
7969
boolean lastNotLetter = true;
8070
for (char ch : name.toCharArray()) {
8171
if (!Character.isLetter(ch)) {
8272
lastNotLetter = true;
8373
continue;
8474
}
85-
rootName.append(lastNotLetter ? Character.toUpperCase(ch) : ch);
75+
clean.append(lastNotLetter ? Character.toUpperCase(ch) : ch);
8676
lastNotLetter = false;
8777
}
88-
return rootName.toString();
78+
return (!clean.isEmpty()) ? clean.toString() : AOT_FEATURE;
8979
}
9080

91-
private ClassName generateSequencedClassName(String rootName, String featureName) {
92-
Assert.hasLength(featureName, "'featureName' must not be empty");
93-
Assert.isTrue(featureName.chars().allMatch(Character::isLetter),
94-
"'featureName' must contain only letters");
95-
String name = addSequence(
96-
rootName + SEPARATOR + StringUtils.capitalize(featureName));
81+
private ClassName generateSequencedClassName(String name) {
82+
name = addSequence(name);
9783
return ClassName.get(ClassUtils.getPackageName(name),
9884
ClassUtils.getShortName(name));
9985
}

spring-core/src/main/java/org/springframework/aot/generate/GeneratedClasses.java

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -58,19 +58,6 @@ public GeneratedClass getOrGenerateClass(JavaFileGenerator javaFileGenerator,
5858
this.classNameGenerator.generateClassName(target, featureName)));
5959
}
6060

61-
@Override
62-
public GeneratedClass getOrGenerateClass(JavaFileGenerator javaFileGenerator,
63-
String target, String featureName) {
64-
65-
Assert.notNull(javaFileGenerator, "'javaFileGenerator' must not be null");
66-
Assert.hasLength(target, "'target' must not be empty");
67-
Assert.hasLength(featureName, "'featureName' must not be empty");
68-
Owner owner = new Owner(javaFileGenerator, target, featureName);
69-
return this.classes.computeIfAbsent(owner,
70-
key -> new GeneratedClass(javaFileGenerator,
71-
this.classNameGenerator.generateClassName(target, featureName)));
72-
}
73-
7461
/**
7562
* Write generated Spring {@code .factories} files to the given
7663
* {@link GeneratedFiles} instance.

spring-core/src/test/java/org/springframework/aot/generate/ClassNameGeneratorTests.java

Lines changed: 15 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -35,18 +35,9 @@ class ClassNameGeneratorTests {
3535
private final ClassNameGenerator generator = new ClassNameGenerator();
3636

3737
@Test
38-
void generateClassNameWhenTargetClassIsNullThrowsException() {
39-
assertThatIllegalArgumentException()
40-
.isThrownBy(
41-
() -> this.generator.generateClassName((Class<?>) null, "Test"))
42-
.withMessage("'target' must not be null");
43-
}
44-
45-
@Test
46-
void generateClassNameWhenTargetStringIsEmptyThrowsException() {
47-
assertThatIllegalArgumentException()
48-
.isThrownBy(() -> this.generator.generateClassName("", "Test"))
49-
.withMessage("'target' must not be empty");
38+
void generateClassNameWhenTargetClassIsNullUsesAotPackage() {
39+
ClassName generated = this.generator.generateClassName((Class<?>) null, "test");
40+
assertThat(generated).hasToString("__.Test");
5041
}
5142

5243
@Test
@@ -58,17 +49,12 @@ void generatedClassNameWhenFeatureIsEmptyThrowsException() {
5849

5950
@Test
6051
void generatedClassNameWhenFeatureIsNotAllLettersThrowsException() {
61-
String expectedMessage = "'featureName' must contain only letters";
62-
assertThatIllegalArgumentException().isThrownBy(
63-
() -> this.generator.generateClassName(InputStream.class, "noway!"))
64-
.withMessage(expectedMessage);
65-
assertThatIllegalArgumentException().isThrownBy(
66-
() -> this.generator.generateClassName(InputStream.class, "1WontWork"))
67-
.withMessage(expectedMessage);
68-
assertThatIllegalArgumentException()
69-
.isThrownBy(
70-
() -> this.generator.generateClassName(InputStream.class, "N0pe"))
71-
.withMessage(expectedMessage);
52+
assertThat(this.generator.generateClassName(InputStream.class, "name!"))
53+
.hasToString("java.io.InputStream__Name");
54+
assertThat(this.generator.generateClassName(InputStream.class, "1NameHere"))
55+
.hasToString("java.io.InputStream__NameHere");
56+
assertThat(this.generator.generateClassName(InputStream.class, "Y0pe"))
57+
.hasToString("java.io.InputStream__YPe");
7258
}
7359

7460
@Test
@@ -80,38 +66,21 @@ void generateClassNameWithClassWhenLowercaseFeatureNameGeneratesName() {
8066

8167
@Test
8268
void generateClassNameWithClassWhenInnerClassGeneratesName() {
83-
ClassName generated = this.generator.generateClassName(TestBean.class,
84-
"EventListener");
85-
assertThat(generated).hasToString(
86-
"org.springframework.aot.generate.ClassNameGeneratorTests_TestBean__EventListener");
69+
ClassName generated = this.generator.generateClassName(TestBean.class, "EventListener");
70+
assertThat(generated)
71+
.hasToString("org.springframework.aot.generate.ClassNameGeneratorTests_TestBean__EventListener");
8772
}
8873

8974
@Test
9075
void generateClassWithClassWhenMultipleCallsGeneratesSequencedName() {
91-
ClassName generated1 = this.generator.generateClassName(InputStream.class,
92-
"bytes");
93-
ClassName generated2 = this.generator.generateClassName(InputStream.class,
94-
"bytes");
95-
ClassName generated3 = this.generator.generateClassName(InputStream.class,
96-
"bytes");
76+
ClassName generated1 = this.generator.generateClassName(InputStream.class, "bytes");
77+
ClassName generated2 = this.generator.generateClassName(InputStream.class, "bytes");
78+
ClassName generated3 = this.generator.generateClassName(InputStream.class, "bytes");
9779
assertThat(generated1).hasToString("java.io.InputStream__Bytes");
9880
assertThat(generated2).hasToString("java.io.InputStream__Bytes1");
9981
assertThat(generated3).hasToString("java.io.InputStream__Bytes2");
10082
}
10183

102-
@Test
103-
void generateClassNameWithStringGeneratesNameUsingOnlyLetters() {
104-
ClassName generated = this.generator.generateClassName("my-bean--factoryStuff",
105-
"beans");
106-
assertThat(generated).hasToString("__.MyBeanFactoryStuff__Beans");
107-
}
108-
109-
@Test
110-
void generateClassNameWithStringWhenNoLettersGeneratesAotName() {
111-
ClassName generated = this.generator.generateClassName("1234!@#", "beans");
112-
assertThat(generated).hasToString("__.Aot__Beans");
113-
}
114-
11584
static class TestBean {
11685

11786
}

spring-core/src/test/java/org/springframework/aot/generate/GeneratedClassesTests.java

Lines changed: 3 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -68,36 +68,12 @@ void getOrGenerateWithClassTargetWhenFeatureIsNullThrowsException() {
6868
.withMessage("'featureName' must not be empty");
6969
}
7070

71-
@Test
72-
void getOrGenerateWithStringTargetWhenJavaFileGeneratorIsNullThrowsException() {
73-
assertThatIllegalArgumentException()
74-
.isThrownBy(() -> this.generatedClasses.getOrGenerateClass(null,
75-
TestTarget.class.getName(), "test"))
76-
.withMessage("'javaFileGenerator' must not be null");
77-
}
78-
79-
@Test
80-
void getOrGenerateWithStringTargetWhenTargetIsNullThrowsException() {
81-
assertThatIllegalArgumentException()
82-
.isThrownBy(() -> this.generatedClasses
83-
.getOrGenerateClass(JAVA_FILE_GENERATOR, (String) null, "test"))
84-
.withMessage("'target' must not be empty");
85-
}
86-
87-
@Test
88-
void getOrGenerateWithStringTargetWhenFeatureIsNullThrowsException() {
89-
assertThatIllegalArgumentException()
90-
.isThrownBy(() -> this.generatedClasses.getOrGenerateClass(
91-
JAVA_FILE_GENERATOR, TestTarget.class.getName(), null))
92-
.withMessage("'featureName' must not be empty");
93-
}
94-
9571
@Test
9672
void getOrGenerateWhenNewReturnsGeneratedMethod() {
9773
GeneratedClass generatedClass1 = this.generatedClasses
9874
.getOrGenerateClass(JAVA_FILE_GENERATOR, TestTarget.class, "one");
99-
GeneratedClass generatedClass2 = this.generatedClasses.getOrGenerateClass(
100-
JAVA_FILE_GENERATOR, TestTarget.class.getName(), "two");
75+
GeneratedClass generatedClass2 = this.generatedClasses
76+
.getOrGenerateClass(JAVA_FILE_GENERATOR, TestTarget.class, "two");
10177
assertThat(generatedClass1).isNotNull().isNotEqualTo(generatedClass2);
10278
assertThat(generatedClass2).isNotNull();
10379
}
@@ -110,7 +86,7 @@ void getOrGenerateWhenRepeatReturnsSameGeneratedMethod() {
11086
GeneratedClass generatedClass2 = generated.getOrGenerateClass(JAVA_FILE_GENERATOR,
11187
TestTarget.class, "one");
11288
GeneratedClass generatedClass3 = generated.getOrGenerateClass(JAVA_FILE_GENERATOR,
113-
TestTarget.class.getName(), "one");
89+
TestTarget.class, "one");
11490
GeneratedClass generatedClass4 = generated.getOrGenerateClass(JAVA_FILE_GENERATOR,
11591
TestTarget.class, "two");
11692
assertThat(generatedClass1).isNotNull().isSameAs(generatedClass2)

0 commit comments

Comments
 (0)