Add updated ApplicationContextAotGenerator
Add `ApplicationContextAotGenerator` implementation that makes use of the new AOT generation APIs. See gh-28414
This commit is contained in:
parent
588d4d8776
commit
702207d9ee
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
* 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 org.springframework.aot.generate.GenerationContext;
|
||||||
|
import org.springframework.beans.factory.BeanFactory;
|
||||||
|
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.context.support.GenericApplicationContext;
|
||||||
|
import org.springframework.javapoet.ClassName;
|
||||||
|
import org.springframework.javapoet.JavaFile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process an {@link ApplicationContext} and its {@link BeanFactory} to generate
|
||||||
|
* code that represents the state of the bean factory, as well as the necessary
|
||||||
|
* hints that can be used at runtime in a constrained environment.
|
||||||
|
*
|
||||||
|
* @author Stephane Nicoll
|
||||||
|
* @author Phillip Webb
|
||||||
|
* @since 6.0
|
||||||
|
*/
|
||||||
|
public class ApplicationContextAotGenerator {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh the specified {@link GenericApplicationContext} and generate the
|
||||||
|
* necessary code to restore the state of its {@link BeanFactory}, using the
|
||||||
|
* specified {@link GenerationContext}.
|
||||||
|
* @param applicationContext the application context to handle
|
||||||
|
* @param generationContext the generation context to use
|
||||||
|
* @param generatedInitializerClassName the class name to use for the
|
||||||
|
* generated application context initializer
|
||||||
|
*/
|
||||||
|
public void generateApplicationContext(GenericApplicationContext applicationContext,
|
||||||
|
GenerationContext generationContext,
|
||||||
|
ClassName generatedInitializerClassName) {
|
||||||
|
|
||||||
|
applicationContext.refreshForAotProcessing();
|
||||||
|
DefaultListableBeanFactory beanFactory = applicationContext
|
||||||
|
.getDefaultListableBeanFactory();
|
||||||
|
ApplicationContextInitializationCodeGenerator codeGenerator = new ApplicationContextInitializationCodeGenerator();
|
||||||
|
new BeanFactoryInitializationContributions(beanFactory).applyTo(generationContext,
|
||||||
|
codeGenerator);
|
||||||
|
JavaFile javaFile = codeGenerator.generateJavaFile(generatedInitializerClassName);
|
||||||
|
generationContext.getGeneratedFiles().addSourceFile(javaFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,103 @@
|
||||||
|
/*
|
||||||
|
* 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.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.lang.model.element.Modifier;
|
||||||
|
|
||||||
|
import org.springframework.aot.generate.GeneratedMethods;
|
||||||
|
import org.springframework.aot.generate.MethodGenerator;
|
||||||
|
import org.springframework.aot.generate.MethodReference;
|
||||||
|
import org.springframework.beans.factory.aot.BeanFactoryInitializationCode;
|
||||||
|
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||||
|
import org.springframework.context.ApplicationContextInitializer;
|
||||||
|
import org.springframework.context.annotation.ContextAnnotationAutowireCandidateResolver;
|
||||||
|
import org.springframework.context.support.GenericApplicationContext;
|
||||||
|
import org.springframework.javapoet.ClassName;
|
||||||
|
import org.springframework.javapoet.CodeBlock;
|
||||||
|
import org.springframework.javapoet.JavaFile;
|
||||||
|
import org.springframework.javapoet.MethodSpec;
|
||||||
|
import org.springframework.javapoet.ParameterizedTypeName;
|
||||||
|
import org.springframework.javapoet.TypeSpec;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal code generator to create the application context initializer.
|
||||||
|
*
|
||||||
|
* @author Phillip Webb
|
||||||
|
* @since 6.0
|
||||||
|
*/
|
||||||
|
class ApplicationContextInitializationCodeGenerator
|
||||||
|
implements BeanFactoryInitializationCode {
|
||||||
|
|
||||||
|
private static final String APPLICATION_CONTEXT_VARIABLE = "applicationContext";
|
||||||
|
|
||||||
|
|
||||||
|
private final GeneratedMethods generatedMethods = new GeneratedMethods();
|
||||||
|
|
||||||
|
private final List<MethodReference> initializers = new ArrayList<>();
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MethodGenerator getMethodGenerator() {
|
||||||
|
return this.generatedMethods;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addInitializer(MethodReference methodReference) {
|
||||||
|
this.initializers.add(methodReference);
|
||||||
|
}
|
||||||
|
|
||||||
|
JavaFile generateJavaFile(ClassName className) {
|
||||||
|
TypeSpec.Builder builder = TypeSpec.classBuilder(className);
|
||||||
|
builder.addJavadoc(
|
||||||
|
"{@link $T} to restore an application context based on previous AOT processing.",
|
||||||
|
ApplicationContextInitializer.class);
|
||||||
|
builder.addModifiers(Modifier.PUBLIC);
|
||||||
|
builder.addSuperinterface(ParameterizedTypeName.get(
|
||||||
|
ApplicationContextInitializer.class, GenericApplicationContext.class));
|
||||||
|
builder.addMethod(generateInitializeMethod());
|
||||||
|
this.generatedMethods.doWithMethodSpecs(builder::addMethod);
|
||||||
|
return JavaFile.builder(className.packageName(), builder.build()).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private MethodSpec generateInitializeMethod() {
|
||||||
|
MethodSpec.Builder builder = MethodSpec.methodBuilder("initialize");
|
||||||
|
builder.addAnnotation(Override.class);
|
||||||
|
builder.addModifiers(Modifier.PUBLIC);
|
||||||
|
builder.addParameter(GenericApplicationContext.class,
|
||||||
|
APPLICATION_CONTEXT_VARIABLE);
|
||||||
|
builder.addCode(generateInitializeCode());
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private CodeBlock generateInitializeCode() {
|
||||||
|
CodeBlock.Builder builder = CodeBlock.builder();
|
||||||
|
builder.addStatement("$T $L = $L.getDefaultListableBeanFactory()",
|
||||||
|
DefaultListableBeanFactory.class, BEAN_FACTORY_VARIABLE,
|
||||||
|
APPLICATION_CONTEXT_VARIABLE);
|
||||||
|
builder.addStatement("$L.setAutowireCandidateResolver(new $T())",
|
||||||
|
BEAN_FACTORY_VARIABLE, ContextAnnotationAutowireCandidateResolver.class);
|
||||||
|
for (MethodReference initializer : this.initializers) {
|
||||||
|
builder.addStatement(
|
||||||
|
initializer.toInvokeCodeBlock(CodeBlock.of(BEAN_FACTORY_VARIABLE)));
|
||||||
|
}
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
/*
|
||||||
|
* 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.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.aot.generate.GenerationContext;
|
||||||
|
import org.springframework.beans.factory.aot.AotFactoriesLoader;
|
||||||
|
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution;
|
||||||
|
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor;
|
||||||
|
import org.springframework.beans.factory.aot.BeanFactoryInitializationCode;
|
||||||
|
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A collection of {@link BeanFactoryInitializationAotContribution AOT
|
||||||
|
* contributions} obtained from {@link BeanFactoryInitializationAotProcessor AOT
|
||||||
|
* processors}.
|
||||||
|
*
|
||||||
|
* @author Phillip Webb
|
||||||
|
* @since 6.0
|
||||||
|
*/
|
||||||
|
class BeanFactoryInitializationContributions {
|
||||||
|
|
||||||
|
private final List<BeanFactoryInitializationAotContribution> contributions;
|
||||||
|
|
||||||
|
|
||||||
|
BeanFactoryInitializationContributions(DefaultListableBeanFactory beanFactory) {
|
||||||
|
this(beanFactory, new AotFactoriesLoader(beanFactory));
|
||||||
|
}
|
||||||
|
|
||||||
|
BeanFactoryInitializationContributions(DefaultListableBeanFactory beanFactory,
|
||||||
|
AotFactoriesLoader loader) {
|
||||||
|
this.contributions = getContributions(beanFactory, getProcessors(loader));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private List<BeanFactoryInitializationAotProcessor> getProcessors(
|
||||||
|
AotFactoriesLoader loader) {
|
||||||
|
List<BeanFactoryInitializationAotProcessor> processors = new ArrayList<>(
|
||||||
|
loader.load(BeanFactoryInitializationAotProcessor.class));
|
||||||
|
processors.add(new RuntimeHintsBeanFactoryInitializationAotProcessor());
|
||||||
|
return Collections.unmodifiableList(processors);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<BeanFactoryInitializationAotContribution> getContributions(
|
||||||
|
DefaultListableBeanFactory beanFactory,
|
||||||
|
List<BeanFactoryInitializationAotProcessor> processors) {
|
||||||
|
List<BeanFactoryInitializationAotContribution> contributions = new ArrayList<>();
|
||||||
|
for (BeanFactoryInitializationAotProcessor processor : processors) {
|
||||||
|
BeanFactoryInitializationAotContribution contribution = processor
|
||||||
|
.processAheadOfTime(beanFactory);
|
||||||
|
if (contribution != null) {
|
||||||
|
contributions.add(contribution);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Collections.unmodifiableList(contributions);
|
||||||
|
}
|
||||||
|
|
||||||
|
void applyTo(GenerationContext generationContext,
|
||||||
|
BeanFactoryInitializationCode beanFactoryInitializationCode) {
|
||||||
|
for (BeanFactoryInitializationAotContribution contribution : this.contributions) {
|
||||||
|
contribution.applyTo(generationContext, beanFactoryInitializationCode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,122 @@
|
||||||
|
/*
|
||||||
|
* 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.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
|
import org.springframework.aot.generate.GenerationContext;
|
||||||
|
import org.springframework.aot.hint.RuntimeHints;
|
||||||
|
import org.springframework.aot.hint.RuntimeHintsRegistrar;
|
||||||
|
import org.springframework.beans.BeanUtils;
|
||||||
|
import org.springframework.beans.factory.aot.AotFactoriesLoader;
|
||||||
|
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution;
|
||||||
|
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor;
|
||||||
|
import org.springframework.beans.factory.aot.BeanFactoryInitializationCode;
|
||||||
|
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
|
||||||
|
import org.springframework.context.annotation.ImportRuntimeHints;
|
||||||
|
import org.springframework.core.log.LogMessage;
|
||||||
|
import org.springframework.lang.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AOT {@code BeanFactoryPostProcessor} that processes
|
||||||
|
* {@link RuntimeHintsRegistrar} implementations declared as
|
||||||
|
* {@code spring.factories} or using
|
||||||
|
* {@link ImportRuntimeHints @ImportRuntimeHints} annotated configuration
|
||||||
|
* classes or bean methods.
|
||||||
|
* <p>
|
||||||
|
* This processor is registered by default in the
|
||||||
|
* {@link ApplicationContextAotGenerator} as it is only useful in an AOT
|
||||||
|
* context.
|
||||||
|
*
|
||||||
|
* @author Brian Clozel
|
||||||
|
* @see ApplicationContextAotGenerator
|
||||||
|
*/
|
||||||
|
class RuntimeHintsBeanFactoryInitializationAotProcessor
|
||||||
|
implements BeanFactoryInitializationAotProcessor {
|
||||||
|
|
||||||
|
private static final Log logger = LogFactory
|
||||||
|
.getLog(RuntimeHintsBeanFactoryInitializationAotProcessor.class);
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BeanFactoryInitializationAotContribution processAheadOfTime(
|
||||||
|
ConfigurableListableBeanFactory beanFactory) {
|
||||||
|
AotFactoriesLoader loader = new AotFactoriesLoader(beanFactory);
|
||||||
|
List<RuntimeHintsRegistrar> registrars = new ArrayList<>(
|
||||||
|
loader.load(RuntimeHintsRegistrar.class));
|
||||||
|
for (String beanName : beanFactory
|
||||||
|
.getBeanNamesForAnnotation(ImportRuntimeHints.class)) {
|
||||||
|
ImportRuntimeHints annotation = beanFactory.findAnnotationOnBean(beanName,
|
||||||
|
ImportRuntimeHints.class);
|
||||||
|
if (annotation != null) {
|
||||||
|
registrars.addAll(extracted(beanName, annotation));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new RuntimeHintsRegistrarContribution(registrars,
|
||||||
|
beanFactory.getBeanClassLoader());
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<RuntimeHintsRegistrar> extracted(String beanName,
|
||||||
|
ImportRuntimeHints annotation) {
|
||||||
|
Class<? extends RuntimeHintsRegistrar>[] registrarClasses = annotation.value();
|
||||||
|
List<RuntimeHintsRegistrar> registrars = new ArrayList<>(registrarClasses.length);
|
||||||
|
for (Class<? extends RuntimeHintsRegistrar> registrarClass : registrarClasses) {
|
||||||
|
logger.trace(
|
||||||
|
LogMessage.format("Loaded [%s] registrar from annotated bean [%s]",
|
||||||
|
registrarClass.getCanonicalName(), beanName));
|
||||||
|
registrars.add(BeanUtils.instantiateClass(registrarClass));
|
||||||
|
}
|
||||||
|
return registrars;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static class RuntimeHintsRegistrarContribution
|
||||||
|
implements BeanFactoryInitializationAotContribution {
|
||||||
|
|
||||||
|
|
||||||
|
private final List<RuntimeHintsRegistrar> registrars;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private final ClassLoader beanClassLoader;
|
||||||
|
|
||||||
|
|
||||||
|
RuntimeHintsRegistrarContribution(List<RuntimeHintsRegistrar> registrars,
|
||||||
|
@Nullable ClassLoader beanClassLoader) {
|
||||||
|
this.registrars = registrars;
|
||||||
|
this.beanClassLoader = beanClassLoader;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void applyTo(GenerationContext generationContext,
|
||||||
|
BeanFactoryInitializationCode beanFactoryInitializationCode) {
|
||||||
|
RuntimeHints hints = generationContext.getRuntimeHints();
|
||||||
|
this.registrars.forEach(registrar -> {
|
||||||
|
logger.trace(LogMessage.format(
|
||||||
|
"Processing RuntimeHints contribution from [%s]",
|
||||||
|
registrar.getClass().getCanonicalName()));
|
||||||
|
registrar.registerHints(hints, this.beanClassLoader);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
/**
|
||||||
|
* AOT support for application contexts.
|
||||||
|
*/
|
||||||
|
@NonNullApi
|
||||||
|
@NonNullFields
|
||||||
|
package org.springframework.context.aot;
|
||||||
|
|
||||||
|
import org.springframework.lang.NonNullApi;
|
||||||
|
import org.springframework.lang.NonNullFields;
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
org.springframework.aot.hint.RuntimeHintsRegistrar= \
|
||||||
|
org.springframework.context.aot.RuntimeHintsBeanFactoryInitializationAotProcessorTests.SampleRuntimeHintsRegistrar
|
Loading…
Reference in New Issue