Add updated ApplicationContextAotGenerator
Add `ApplicationContextAotGenerator` implementation that makes use of the new AOT generation APIs. See gh-28414
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user