Add AOT processing of bean aliases

This commit adds AOT processing of bean aliases.

Closes gh-29391
This commit is contained in:
Sébastien Deleuze 2022-11-02 08:27:30 +01:00
parent bfe37c290e
commit 403cfefc28
4 changed files with 85 additions and 15 deletions

View File

@ -30,12 +30,14 @@ import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.javapoet.ClassName; import org.springframework.javapoet.ClassName;
import org.springframework.javapoet.CodeBlock; import org.springframework.javapoet.CodeBlock;
import org.springframework.javapoet.MethodSpec; import org.springframework.javapoet.MethodSpec;
import org.springframework.util.MultiValueMap;
/** /**
* AOT contribution from a {@link BeanRegistrationsAotProcessor} used to * AOT contribution from a {@link BeanRegistrationsAotProcessor} used to
* register bean definitions. * register bean definitions and aliases.
* *
* @author Phillip Webb * @author Phillip Webb
* @author Sebastien Deleuze
* @since 6.0 * @since 6.0
* @see BeanRegistrationsAotProcessor * @see BeanRegistrationsAotProcessor
*/ */
@ -46,11 +48,14 @@ class BeanRegistrationsAotContribution
private final Map<String, BeanDefinitionMethodGenerator> registrations; private final Map<String, BeanDefinitionMethodGenerator> registrations;
private final MultiValueMap<String, String> aliases;
BeanRegistrationsAotContribution( BeanRegistrationsAotContribution(
Map<String, BeanDefinitionMethodGenerator> registrations) { Map<String, BeanDefinitionMethodGenerator> registrations, MultiValueMap<String, String> aliases) {
this.registrations = registrations; this.registrations = registrations;
this.aliases = aliases;
} }
@ -64,12 +69,15 @@ class BeanRegistrationsAotContribution
type.addModifiers(Modifier.PUBLIC); type.addModifiers(Modifier.PUBLIC);
}); });
BeanRegistrationsCodeGenerator codeGenerator = new BeanRegistrationsCodeGenerator(generatedClass); BeanRegistrationsCodeGenerator codeGenerator = new BeanRegistrationsCodeGenerator(generatedClass);
GeneratedMethod generatedMethod = codeGenerator.getMethods().add("registerBeanDefinitions", method -> GeneratedMethod generatedBeanDefinitionsMethod = codeGenerator.getMethods().add("registerBeanDefinitions", method ->
generateRegisterMethod(method, generationContext, codeGenerator)); generateRegisterBeanDefinitionsMethod(method, generationContext, codeGenerator));
beanFactoryInitializationCode.addInitializer(generatedMethod.toMethodReference()); beanFactoryInitializationCode.addInitializer(generatedBeanDefinitionsMethod.toMethodReference());
GeneratedMethod generatedAliasesMethod = codeGenerator.getMethods().add("registerAliases",
this::generateRegisterAliasesMethod);
beanFactoryInitializationCode.addInitializer(generatedAliasesMethod.toMethodReference());
} }
private void generateRegisterMethod(MethodSpec.Builder method, private void generateRegisterBeanDefinitionsMethod(MethodSpec.Builder method,
GenerationContext generationContext, GenerationContext generationContext,
BeanRegistrationsCode beanRegistrationsCode) { BeanRegistrationsCode beanRegistrationsCode) {
@ -91,6 +99,18 @@ class BeanRegistrationsAotContribution
method.addCode(code.build()); method.addCode(code.build());
} }
private void generateRegisterAliasesMethod(MethodSpec.Builder method) {
method.addJavadoc("Register the aliases.");
method.addModifiers(Modifier.PUBLIC);
method.addParameter(DefaultListableBeanFactory.class,
BEAN_FACTORY_PARAMETER_NAME);
CodeBlock.Builder code = CodeBlock.builder();
this.aliases.forEach((beanName, beanAliases) ->
beanAliases.forEach(alias -> code.addStatement("$L.registerAlias($S, $S)", BEAN_FACTORY_PARAMETER_NAME,
beanName, alias)));
method.addCode(code.build());
}
/** /**
* {@link BeanRegistrationsCode} with generation support. * {@link BeanRegistrationsCode} with generation support.

View File

@ -21,12 +21,15 @@ import java.util.Map;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.RegisteredBean; import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
/** /**
* {@link BeanFactoryInitializationAotProcessor} that contributes code to * {@link BeanFactoryInitializationAotProcessor} that contributes code to
* register beans. * register beans.
* *
* @author Phillip Webb * @author Phillip Webb
* @author Sebastien Deleuze
* @since 6.0 * @since 6.0
*/ */
class BeanRegistrationsAotProcessor implements BeanFactoryInitializationAotProcessor { class BeanRegistrationsAotProcessor implements BeanFactoryInitializationAotProcessor {
@ -36,6 +39,7 @@ class BeanRegistrationsAotProcessor implements BeanFactoryInitializationAotProce
BeanDefinitionMethodGeneratorFactory beanDefinitionMethodGeneratorFactory = BeanDefinitionMethodGeneratorFactory beanDefinitionMethodGeneratorFactory =
new BeanDefinitionMethodGeneratorFactory(beanFactory); new BeanDefinitionMethodGeneratorFactory(beanFactory);
Map<String, BeanDefinitionMethodGenerator> registrations = new LinkedHashMap<>(); Map<String, BeanDefinitionMethodGenerator> registrations = new LinkedHashMap<>();
MultiValueMap<String, String> aliases = new LinkedMultiValueMap<>();
for (String beanName : beanFactory.getBeanDefinitionNames()) { for (String beanName : beanFactory.getBeanDefinitionNames()) {
RegisteredBean registeredBean = RegisteredBean.of(beanFactory, beanName); RegisteredBean registeredBean = RegisteredBean.of(beanFactory, beanName);
BeanDefinitionMethodGenerator beanDefinitionMethodGenerator = beanDefinitionMethodGeneratorFactory BeanDefinitionMethodGenerator beanDefinitionMethodGenerator = beanDefinitionMethodGeneratorFactory
@ -43,11 +47,14 @@ class BeanRegistrationsAotProcessor implements BeanFactoryInitializationAotProce
if (beanDefinitionMethodGenerator != null) { if (beanDefinitionMethodGenerator != null) {
registrations.put(beanName, beanDefinitionMethodGenerator); registrations.put(beanName, beanDefinitionMethodGenerator);
} }
for (String alias : beanFactory.getAliases(beanName)) {
aliases.add(beanName, alias);
}
} }
if (registrations.isEmpty()) { if (registrations.isEmpty()) {
return null; return null;
} }
return new BeanRegistrationsAotContribution(registrations); return new BeanRegistrationsAotContribution(registrations, aliases);
} }
} }

View File

@ -42,9 +42,12 @@ import org.springframework.core.test.io.support.MockSpringFactoriesLoader;
import org.springframework.core.test.tools.Compiled; import org.springframework.core.test.tools.Compiled;
import org.springframework.core.test.tools.SourceFile; import org.springframework.core.test.tools.SourceFile;
import org.springframework.core.test.tools.TestCompiler; import org.springframework.core.test.tools.TestCompiler;
import org.springframework.javapoet.ClassName;
import org.springframework.javapoet.CodeBlock; import org.springframework.javapoet.CodeBlock;
import org.springframework.javapoet.MethodSpec; import org.springframework.javapoet.MethodSpec;
import org.springframework.javapoet.ParameterizedTypeName; import org.springframework.javapoet.ParameterizedTypeName;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -52,6 +55,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* Tests for {@link BeanRegistrationsAotContribution}. * Tests for {@link BeanRegistrationsAotContribution}.
* *
* @author Phillip Webb * @author Phillip Webb
* @author Sebastien Deleuze
*/ */
class BeanRegistrationsAotContributionTests { class BeanRegistrationsAotContributionTests {
@ -84,7 +88,7 @@ class BeanRegistrationsAotContributionTests {
Collections.emptyList()); Collections.emptyList());
registrations.put("testBean", generator); registrations.put("testBean", generator);
BeanRegistrationsAotContribution contribution = new BeanRegistrationsAotContribution( BeanRegistrationsAotContribution contribution = new BeanRegistrationsAotContribution(
registrations); registrations, new LinkedMultiValueMap<>());
contribution.applyTo(this.generationContext, this.beanFactoryInitializationCode); contribution.applyTo(this.generationContext, this.beanFactoryInitializationCode);
compile((consumer, compiled) -> { compile((consumer, compiled) -> {
DefaultListableBeanFactory freshBeanFactory = new DefaultListableBeanFactory(); DefaultListableBeanFactory freshBeanFactory = new DefaultListableBeanFactory();
@ -93,6 +97,27 @@ class BeanRegistrationsAotContributionTests {
}); });
} }
@Test
void applyToAppliesContributionWithAliases() {
Map<String, BeanDefinitionMethodGenerator> registrations = new LinkedHashMap<>();
RegisteredBean registeredBean = registerBean(
new RootBeanDefinition(TestBean.class));
BeanDefinitionMethodGenerator generator = new BeanDefinitionMethodGenerator(
this.methodGeneratorFactory, registeredBean, null,
Collections.emptyList());
registrations.put("testBean", generator);
MultiValueMap<String, String> aliases = new LinkedMultiValueMap<>();
aliases.add("testBean", "testAlias");
BeanRegistrationsAotContribution contribution = new BeanRegistrationsAotContribution(
registrations, aliases);
contribution.applyTo(this.generationContext, this.beanFactoryInitializationCode);
compile((consumer, compiled) -> {
DefaultListableBeanFactory freshBeanFactory = new DefaultListableBeanFactory();
consumer.accept(freshBeanFactory);
assertThat(freshBeanFactory.getAliases("testBean")).containsExactly("testAlias");
});
}
@Test @Test
void applyToWhenHasNameGeneratesPrefixedFeatureName() { void applyToWhenHasNameGeneratesPrefixedFeatureName() {
this.generationContext = new TestGenerationContext( this.generationContext = new TestGenerationContext(
@ -106,7 +131,7 @@ class BeanRegistrationsAotContributionTests {
Collections.emptyList()); Collections.emptyList());
registrations.put("testBean", generator); registrations.put("testBean", generator);
BeanRegistrationsAotContribution contribution = new BeanRegistrationsAotContribution( BeanRegistrationsAotContribution contribution = new BeanRegistrationsAotContribution(
registrations); registrations, new LinkedMultiValueMap<>());
contribution.applyTo(this.generationContext, this.beanFactoryInitializationCode); contribution.applyTo(this.generationContext, this.beanFactoryInitializationCode);
compile((consumer, compiled) -> { compile((consumer, compiled) -> {
SourceFile sourceFile = compiled.getSourceFile(".*BeanDefinitions"); SourceFile sourceFile = compiled.getSourceFile(".*BeanDefinitions");
@ -136,7 +161,7 @@ class BeanRegistrationsAotContributionTests {
}; };
registrations.put("testBean", generator); registrations.put("testBean", generator);
BeanRegistrationsAotContribution contribution = new BeanRegistrationsAotContribution( BeanRegistrationsAotContribution contribution = new BeanRegistrationsAotContribution(
registrations); registrations, new LinkedMultiValueMap<>());
contribution.applyTo(this.generationContext, this.beanFactoryInitializationCode); contribution.applyTo(this.generationContext, this.beanFactoryInitializationCode);
assertThat(beanRegistrationsCodes).hasSize(1); assertThat(beanRegistrationsCodes).hasSize(1);
BeanRegistrationsCode actual = beanRegistrationsCodes.get(0); BeanRegistrationsCode actual = beanRegistrationsCodes.get(0);
@ -152,17 +177,21 @@ class BeanRegistrationsAotContributionTests {
@SuppressWarnings({ "unchecked", "cast" }) @SuppressWarnings({ "unchecked", "cast" })
private void compile( private void compile(
BiConsumer<Consumer<DefaultListableBeanFactory>, Compiled> result) { BiConsumer<Consumer<DefaultListableBeanFactory>, Compiled> result) {
MethodReference methodReference = this.beanFactoryInitializationCode MethodReference beanRegistrationsMethodReference = this.beanFactoryInitializationCode
.getInitializers().get(0); .getInitializers().get(0);
MethodReference aliasesMethodReference = this.beanFactoryInitializationCode
.getInitializers().get(1);
this.beanFactoryInitializationCode.getTypeBuilder().set(type -> { this.beanFactoryInitializationCode.getTypeBuilder().set(type -> {
CodeBlock methodInvocation = methodReference.toInvokeCodeBlock( ArgumentCodeGenerator beanFactory = ArgumentCodeGenerator.of(DefaultListableBeanFactory.class, "beanFactory");
ArgumentCodeGenerator.of(DefaultListableBeanFactory.class, "beanFactory"), ClassName className = this.beanFactoryInitializationCode.getClassName();
this.beanFactoryInitializationCode.getClassName()); CodeBlock beanRegistrationsMethodInvocation = beanRegistrationsMethodReference.toInvokeCodeBlock(beanFactory, className);
CodeBlock aliasesMethodInvocation = aliasesMethodReference.toInvokeCodeBlock(beanFactory, className);
type.addModifiers(Modifier.PUBLIC); type.addModifiers(Modifier.PUBLIC);
type.addSuperinterface(ParameterizedTypeName.get(Consumer.class, DefaultListableBeanFactory.class)); type.addSuperinterface(ParameterizedTypeName.get(Consumer.class, DefaultListableBeanFactory.class));
type.addMethod(MethodSpec.methodBuilder("accept").addModifiers(Modifier.PUBLIC) type.addMethod(MethodSpec.methodBuilder("accept").addModifiers(Modifier.PUBLIC)
.addParameter(DefaultListableBeanFactory.class, "beanFactory") .addParameter(DefaultListableBeanFactory.class, "beanFactory")
.addStatement(methodInvocation) .addStatement(beanRegistrationsMethodInvocation)
.addStatement(aliasesMethodInvocation)
.build()); .build());
}); });
this.generationContext.writeGeneratedContent(); this.generationContext.writeGeneratedContent();

View File

@ -30,6 +30,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* Tests for {@link BeanRegistrationsAotProcessor}. * Tests for {@link BeanRegistrationsAotProcessor}.
* *
* @author Phillip Webb * @author Phillip Webb
* @author Sebastien Deleuze
*/ */
class BeanRegistrationsAotProcessorTests { class BeanRegistrationsAotProcessorTests {
@ -53,4 +54,17 @@ class BeanRegistrationsAotProcessorTests {
.asInstanceOf(InstanceOfAssertFactories.MAP).containsKeys("b1", "b2"); .asInstanceOf(InstanceOfAssertFactories.MAP).containsKeys("b1", "b2");
} }
@Test
void processAheadOfTimeReturnsBeanRegistrationsAotContributionWithAliases() {
BeanRegistrationsAotProcessor processor = new BeanRegistrationsAotProcessor();
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.registerBeanDefinition("test", new RootBeanDefinition(TestBean.class));
beanFactory.registerAlias("test", "testAlias");
BeanRegistrationsAotContribution contribution = processor
.processAheadOfTime(beanFactory);
assertThat(contribution).extracting("aliases")
.asInstanceOf(InstanceOfAssertFactories.MAP).hasEntrySatisfying("test", value ->
assertThat(value).asList().singleElement().isEqualTo("testAlias"));
}
} }