Add updated ApplicationContextAotGenerator

Add `ApplicationContextAotGenerator` implementation that makes
use of the new AOT generation APIs.

See gh-28414
This commit is contained in:
Phillip Webb
2022-05-04 20:26:37 -07:00
parent 588d4d8776
commit 702207d9ee
8 changed files with 818 additions and 0 deletions

View File

@@ -0,0 +1,242 @@
/*
* Copyright 2002-2022 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.aot;
import java.util.function.BiConsumer;
import org.junit.jupiter.api.Test;
import org.springframework.aot.generate.DefaultGenerationContext;
import org.springframework.aot.generate.InMemoryGeneratedFiles;
import org.springframework.aot.test.generator.compile.Compiled;
import org.springframework.aot.test.generator.compile.TestCompiler;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor;
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution;
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor;
import org.springframework.beans.factory.aot.BeanRegistrationAotContribution;
import org.springframework.beans.factory.aot.BeanRegistrationAotProcessor;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.RegisteredBean;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.annotation.AnnotationConfigUtils;
import org.springframework.context.annotation.CommonAnnotationBeanPostProcessor;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.context.testfixture.context.generator.SimpleComponent;
import org.springframework.context.testfixture.context.generator.annotation.AutowiredComponent;
import org.springframework.context.testfixture.context.generator.annotation.InitDestroyComponent;
import org.springframework.javapoet.ClassName;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ApplicationContextAotGenerator}.
*
* @author Stephane Nicoll
* @author Phillip Webb
*/
class ApplicationContextAotGeneratorTests {
private static final ClassName MAIN_GENERATED_TYPE = ClassName.get("__",
"TestInitializer");
@Test
void generateApplicationContextWhenHasSimpleBean() {
GenericApplicationContext applicationContext = new GenericApplicationContext();
applicationContext.registerBeanDefinition("test",
new RootBeanDefinition(SimpleComponent.class));
testCompiledResult(applicationContext, (initializer, compiled) -> {
GenericApplicationContext freshApplicationContext = toFreshApplicationContext(
initializer);
assertThat(freshApplicationContext.getBeanDefinitionNames())
.containsOnly("test");
assertThat(freshApplicationContext.getBean("test"))
.isInstanceOf(SimpleComponent.class);
});
}
@Test
void generateApplicationContextWhenHasAutowiring() {
GenericApplicationContext applicationContext = new GenericApplicationContext();
applicationContext.registerBeanDefinition(
AnnotationConfigUtils.AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME,
BeanDefinitionBuilder
.rootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class)
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE).getBeanDefinition());
applicationContext.registerBeanDefinition("autowiredComponent",
new RootBeanDefinition(AutowiredComponent.class));
applicationContext.registerBeanDefinition("number",
BeanDefinitionBuilder.rootBeanDefinition(Integer.class, "valueOf")
.addConstructorArgValue("42").getBeanDefinition());
testCompiledResult(applicationContext, (initializer, compiled) -> {
GenericApplicationContext freshApplicationContext = toFreshApplicationContext(
initializer);
assertThat(freshApplicationContext.getBeanDefinitionNames())
.containsOnly("autowiredComponent", "number");
AutowiredComponent bean = freshApplicationContext
.getBean(AutowiredComponent.class);
assertThat(bean.getEnvironment())
.isSameAs(freshApplicationContext.getEnvironment());
assertThat(bean.getCounter()).isEqualTo(42);
});
}
@Test
void generateApplicationContextWhenHasInitDestroyMethods() {
GenericApplicationContext applicationContext = new GenericApplicationContext();
applicationContext.registerBeanDefinition(
AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME,
BeanDefinitionBuilder
.rootBeanDefinition(CommonAnnotationBeanPostProcessor.class)
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE).getBeanDefinition());
applicationContext.registerBeanDefinition("initDestroyComponent",
new RootBeanDefinition(InitDestroyComponent.class));
testCompiledResult(applicationContext, (initializer, compiled) -> {
GenericApplicationContext freshApplicationContext = toFreshApplicationContext(
initializer);
assertThat(freshApplicationContext.getBeanDefinitionNames())
.containsOnly("initDestroyComponent");
InitDestroyComponent bean = freshApplicationContext
.getBean(InitDestroyComponent.class);
assertThat(bean.events).containsExactly("init");
freshApplicationContext.close();
assertThat(bean.events).containsExactly("init", "destroy");
});
}
@Test
void generateApplicationContextWhenHasMultipleInitDestroyMethods() {
GenericApplicationContext applicationContext = new GenericApplicationContext();
applicationContext.registerBeanDefinition(
AnnotationConfigUtils.COMMON_ANNOTATION_PROCESSOR_BEAN_NAME,
BeanDefinitionBuilder
.rootBeanDefinition(CommonAnnotationBeanPostProcessor.class)
.setRole(BeanDefinition.ROLE_INFRASTRUCTURE).getBeanDefinition());
RootBeanDefinition beanDefinition = new RootBeanDefinition(
InitDestroyComponent.class);
beanDefinition.setInitMethodName("customInit");
beanDefinition.setDestroyMethodName("customDestroy");
applicationContext.registerBeanDefinition("initDestroyComponent", beanDefinition);
testCompiledResult(applicationContext, (initializer, compiled) -> {
GenericApplicationContext freshApplicationContext = toFreshApplicationContext(
initializer);
assertThat(freshApplicationContext.getBeanDefinitionNames())
.containsOnly("initDestroyComponent");
InitDestroyComponent bean = freshApplicationContext
.getBean(InitDestroyComponent.class);
assertThat(bean.events).containsExactly("customInit", "init");
freshApplicationContext.close();
assertThat(bean.events).containsExactly("customInit", "init", "customDestroy",
"destroy");
});
}
@Test
void generateApplicationContextWhenHasNoAotContributions() {
GenericApplicationContext applicationContext = new GenericApplicationContext();
testCompiledResult(applicationContext, (initializer, compiled) -> {
GenericApplicationContext freshApplicationContext = toFreshApplicationContext(
initializer);
assertThat(freshApplicationContext.getBeanDefinitionNames()).isEmpty();
assertThat(compiled.getSourceFile()).contains(
"beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver())");
});
}
@Test
void generateApplicationContextWhenHasBeanFactoryInitializationAotProcessorExcludesProcessor() {
GenericApplicationContext applicationContext = new GenericApplicationContext();
applicationContext.registerBeanDefinition("test",
new RootBeanDefinition(NoOpBeanFactoryInitializationAotProcessor.class));
testCompiledResult(applicationContext, (initializer, compiled) -> {
GenericApplicationContext freshApplicationContext = toFreshApplicationContext(
initializer);
assertThat(freshApplicationContext.getBeanDefinitionNames()).isEmpty();
});
}
@Test
void generateApplicationContextWhenHasBeanRegistrationAotProcessorExcludesProcessor() {
GenericApplicationContext applicationContext = new GenericApplicationContext();
applicationContext.registerBeanDefinition("test",
new RootBeanDefinition(NoOpBeanRegistrationAotProcessor.class));
testCompiledResult(applicationContext, (initializer, compiled) -> {
GenericApplicationContext freshApplicationContext = toFreshApplicationContext(
initializer);
assertThat(freshApplicationContext.getBeanDefinitionNames()).isEmpty();
});
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private void testCompiledResult(GenericApplicationContext applicationContext,
BiConsumer<ApplicationContextInitializer<GenericApplicationContext>, Compiled> result) {
ApplicationContextAotGenerator generator = new ApplicationContextAotGenerator();
InMemoryGeneratedFiles generatedFiles = new InMemoryGeneratedFiles();
DefaultGenerationContext generationContext = new DefaultGenerationContext(
generatedFiles);
generator.generateApplicationContext(applicationContext, generationContext,
MAIN_GENERATED_TYPE);
generationContext.writeGeneratedContent();
TestCompiler.forSystem().withFiles(generatedFiles)
.compile(compiled -> result.accept(
compiled.getInstance(ApplicationContextInitializer.class),
compiled));
}
private GenericApplicationContext toFreshApplicationContext(
ApplicationContextInitializer<GenericApplicationContext> initializer) {
GenericApplicationContext freshApplicationContext = new GenericApplicationContext();
initializer.initialize(freshApplicationContext);
freshApplicationContext.refresh();
return freshApplicationContext;
}
static class NoOpBeanFactoryInitializationAotProcessor
implements BeanFactoryPostProcessor, BeanFactoryInitializationAotProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
throws BeansException {
}
@Override
public BeanFactoryInitializationAotContribution processAheadOfTime(
ConfigurableListableBeanFactory beanFactory) {
return null;
}
}
static class NoOpBeanRegistrationAotProcessor
implements BeanPostProcessor, BeanRegistrationAotProcessor {
@Override
public BeanRegistrationAotContribution processAheadOfTime(
RegisteredBean registeredBean) {
return null;
}
}
}

View File

@@ -0,0 +1,197 @@
/*
* Copyright 2002-2022 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.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.context.aot;
import java.io.IOException;
import java.net.URL;
import java.util.Enumeration;
import java.util.stream.Stream;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.aot.generate.DefaultGenerationContext;
import org.springframework.aot.generate.GenerationContext;
import org.springframework.aot.generate.InMemoryGeneratedFiles;
import org.springframework.aot.hint.ResourceBundleHint;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.beans.BeanInstantiationException;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.annotation.AnnotationConfigUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportRuntimeHints;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.javapoet.ClassName;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
/**
* Tests for {@link RuntimeHintsBeanFactoryInitializationAotProcessor}.
*
* @author Brian Clozel
*/
class RuntimeHintsBeanFactoryInitializationAotProcessorTests {
private static final ClassName MAIN_GENERATED_TYPE = ClassName.get("__",
"TestInitializer");
private GenerationContext generationContext;
private ApplicationContextAotGenerator generator;
@BeforeEach
void setup() {
this.generationContext = new DefaultGenerationContext(
new InMemoryGeneratedFiles());
this.generator = new ApplicationContextAotGenerator();
}
@Test
void shouldProcessRegistrarOnConfiguration() {
GenericApplicationContext applicationContext = createApplicationContext(
ConfigurationWithHints.class);
this.generator.generateApplicationContext(applicationContext,
this.generationContext, MAIN_GENERATED_TYPE);
assertThatSampleRegistrarContributed();
}
@Test
void shouldProcessRegistrarOnBeanMethod() {
GenericApplicationContext applicationContext = createApplicationContext(
ConfigurationWithBeanDeclaringHints.class);
this.generator.generateApplicationContext(applicationContext,
this.generationContext, MAIN_GENERATED_TYPE);
assertThatSampleRegistrarContributed();
}
@Test
void shouldProcessRegistrarInSpringFactory() {
GenericApplicationContext applicationContext = createApplicationContext();
applicationContext.setClassLoader(
new TestSpringFactoriesClassLoader("test-runtime-hints-aot.factories"));
this.generator.generateApplicationContext(applicationContext,
this.generationContext, MAIN_GENERATED_TYPE);
assertThatSampleRegistrarContributed();
}
@Test
void shouldRejectRuntimeHintsRegistrarWithoutDefaultConstructor() {
GenericApplicationContext applicationContext = createApplicationContext(
ConfigurationWithIllegalRegistrar.class);
assertThatThrownBy(() -> this.generator.generateApplicationContext(
applicationContext, this.generationContext, MAIN_GENERATED_TYPE))
.isInstanceOf(BeanInstantiationException.class);
}
private void assertThatSampleRegistrarContributed() {
Stream<ResourceBundleHint> bundleHints = this.generationContext.getRuntimeHints()
.resources().resourceBundles();
assertThat(bundleHints)
.anyMatch(bundleHint -> "sample".equals(bundleHint.getBaseName()));
}
private GenericApplicationContext createApplicationContext(
Class<?>... configClasses) {
GenericApplicationContext applicationContext = new GenericApplicationContext();
AnnotationConfigUtils.registerAnnotationConfigProcessors(applicationContext);
for (Class<?> configClass : configClasses) {
applicationContext.registerBeanDefinition(configClass.getSimpleName(),
new RootBeanDefinition(configClass));
}
return applicationContext;
}
@ImportRuntimeHints(SampleRuntimeHintsRegistrar.class)
@Configuration(proxyBeanMethods = false)
static class ConfigurationWithHints {
}
@Configuration(proxyBeanMethods = false)
static class ConfigurationWithBeanDeclaringHints {
@Bean
@ImportRuntimeHints(SampleRuntimeHintsRegistrar.class)
SampleBean sampleBean() {
return new SampleBean();
}
}
public static class SampleRuntimeHintsRegistrar implements RuntimeHintsRegistrar {
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
hints.resources().registerResourceBundle("sample");
}
}
static class SampleBean {
}
@ImportRuntimeHints(IllegalRuntimeHintsRegistrar.class)
@Configuration(proxyBeanMethods = false)
static class ConfigurationWithIllegalRegistrar {
}
public static class IllegalRuntimeHintsRegistrar implements RuntimeHintsRegistrar {
public IllegalRuntimeHintsRegistrar(String arg) {
}
@Override
public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
hints.resources().registerResourceBundle("sample");
}
}
static class TestSpringFactoriesClassLoader extends ClassLoader {
private final String factoriesName;
TestSpringFactoriesClassLoader(String factoriesName) {
super(Thread.currentThread().getContextClassLoader());
this.factoriesName = factoriesName;
}
@Override
public Enumeration<URL> getResources(String name) throws IOException {
if ("META-INF/spring/aot.factories".equals(name)) {
return super.getResources(
"org/springframework/context/aot/" + this.factoriesName);
}
return super.getResources(name);
}
}
}