Introduce ApplicationContextAotGenerator

This commit introduces a way to process a GenericApplicationContext
ahead of time. Components that can contribute in that phase are
invoked, and their contributions are recorded in the
GeneratedTypeContext.

This commit also expands BeanFactoryContribution so that it can exclude
bean definitions that are no longer required.

Closes gh-28150
This commit is contained in:
Stephane Nicoll
2022-03-06 18:40:27 +01:00
parent 30cd14d61d
commit 9b07457d06
13 changed files with 802 additions and 0 deletions

View File

@@ -0,0 +1,172 @@
/*
* 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.generator;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.function.BiPredicate;
import java.util.stream.Stream;
import javax.lang.model.element.Modifier;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aot.generator.GeneratedType;
import org.springframework.aot.generator.GeneratedTypeContext;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.generator.AotContributingBeanFactoryPostProcessor;
import org.springframework.beans.factory.generator.AotContributingBeanPostProcessor;
import org.springframework.beans.factory.generator.BeanDefinitionsContribution;
import org.springframework.beans.factory.generator.BeanFactoryContribution;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.OrderComparator;
import org.springframework.javapoet.CodeBlock;
import org.springframework.javapoet.MethodSpec;
import org.springframework.javapoet.ParameterizedTypeName;
/**
* 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
* @since 6.0
*/
public class ApplicationContextAotGenerator {
private static final Log logger = LogFactory.getLog(ApplicationContextAotGenerator.class);
/**
* Refresh the specified {@link GenericApplicationContext} and generate the
* necessary code to restore the state of its {@link BeanFactory}, using the
* specified {@link GeneratedTypeContext}.
* @param applicationContext the application context to handle
* @param generationContext the generation context to use
*/
public void generateApplicationContext(GenericApplicationContext applicationContext,
GeneratedTypeContext generationContext) {
applicationContext.refreshForAotProcessing();
DefaultListableBeanFactory beanFactory = applicationContext.getDefaultListableBeanFactory();
List<BeanFactoryContribution> contributions = resolveBeanFactoryContributions(beanFactory);
filterBeanFactory(contributions, beanFactory);
ApplicationContextInitialization applicationContextInitialization = new ApplicationContextInitialization(generationContext);
applyContributions(contributions, applicationContextInitialization);
GeneratedType mainGeneratedType = generationContext.getMainGeneratedType();
mainGeneratedType.customizeType(type -> type.addSuperinterface(ParameterizedTypeName.get(
ApplicationContextInitializer.class, GenericApplicationContext.class)));
mainGeneratedType.addMethod(initializeMethod(applicationContextInitialization.toCodeBlock()));
}
private MethodSpec.Builder initializeMethod(CodeBlock methodBody) {
MethodSpec.Builder method = MethodSpec.methodBuilder("initialize").addModifiers(Modifier.PUBLIC)
.addParameter(GenericApplicationContext.class, "context").addAnnotation(Override.class);
method.addCode(methodBody);
return method;
}
private void filterBeanFactory(List<BeanFactoryContribution> contributions, DefaultListableBeanFactory beanFactory) {
BiPredicate<String, BeanDefinition> filter = Stream.concat(Stream.of(aotContributingExcludeFilter()),
contributions.stream().map(BeanFactoryContribution::getBeanDefinitionExcludeFilter))
.filter(Objects::nonNull).reduce((n, d) -> false, BiPredicate::or);
for (String beanName : beanFactory.getBeanDefinitionNames()) {
BeanDefinition bd = beanFactory.getMergedBeanDefinition(beanName);
if (filter.test(beanName, bd)) {
if (logger.isDebugEnabled()) {
logger.debug("Filtering out bean with name" + beanName + ": " + bd);
}
beanFactory.removeBeanDefinition(beanName);
}
}
}
// TODO: is this right?
private BiPredicate<String, BeanDefinition> aotContributingExcludeFilter() {
return (beanName, beanDefinition) -> {
Class<?> type = beanDefinition.getResolvableType().toClass();
return AotContributingBeanFactoryPostProcessor.class.isAssignableFrom(type) ||
AotContributingBeanPostProcessor.class.isAssignableFrom(type);
};
}
private void applyContributions(List<BeanFactoryContribution> contributions,
ApplicationContextInitialization initialization) {
for (BeanFactoryContribution contribution : contributions) {
contribution.applyTo(initialization);
}
}
/**
* Resolve the {@link BeanFactoryContribution} available in the specified
* bean factory. Infrastructure is contributed first, and bean definitions
* registration last.
* @param beanFactory the bean factory to process
* @return the contribution to apply
* @see InfrastructureContribution
* @see BeanDefinitionsContribution
*/
private List<BeanFactoryContribution> resolveBeanFactoryContributions(DefaultListableBeanFactory beanFactory) {
List<BeanFactoryContribution> contributions = new ArrayList<>();
contributions.add(new InfrastructureContribution());
List<AotContributingBeanFactoryPostProcessor> postProcessors = getAotContributingBeanFactoryPostProcessors(beanFactory);
for (AotContributingBeanFactoryPostProcessor postProcessor : postProcessors) {
BeanFactoryContribution contribution = postProcessor.contribute(beanFactory);
if (contribution != null) {
contributions.add(contribution);
}
}
contributions.add(new BeanDefinitionsContribution(beanFactory));
return contributions;
}
private static List<AotContributingBeanFactoryPostProcessor> getAotContributingBeanFactoryPostProcessors(DefaultListableBeanFactory beanFactory) {
String[] postProcessorNames = beanFactory.getBeanNamesForType(AotContributingBeanFactoryPostProcessor.class, true, false);
List<AotContributingBeanFactoryPostProcessor> postProcessors = new ArrayList<>();
for (String ppName : postProcessorNames) {
postProcessors.add(beanFactory.getBean(ppName, AotContributingBeanFactoryPostProcessor.class));
}
sortPostProcessors(postProcessors, beanFactory);
return postProcessors;
}
private static void sortPostProcessors(List<?> postProcessors, ConfigurableListableBeanFactory beanFactory) {
// Nothing to sort?
if (postProcessors.size() <= 1) {
return;
}
Comparator<Object> comparatorToUse = null;
if (beanFactory instanceof DefaultListableBeanFactory) {
comparatorToUse = ((DefaultListableBeanFactory) beanFactory).getDependencyComparator();
}
if (comparatorToUse == null) {
comparatorToUse = OrderComparator.INSTANCE;
}
postProcessors.sort(comparatorToUse);
}
}

View File

@@ -0,0 +1,35 @@
/*
* 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.generator;
import org.springframework.aot.generator.GeneratedTypeContext;
import org.springframework.beans.factory.generator.BeanFactoryInitialization;
import org.springframework.context.ApplicationContext;
/**
* The initialization of an {@link ApplicationContext}.
*
* @author Andy Wilkinson
* @since 6.0
*/
public class ApplicationContextInitialization extends BeanFactoryInitialization {
public ApplicationContextInitialization(GeneratedTypeContext generatedTypeContext) {
super(generatedTypeContext);
}
}

View File

@@ -0,0 +1,43 @@
/*
* 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.generator;
import org.springframework.beans.factory.generator.BeanFactoryContribution;
import org.springframework.beans.factory.generator.BeanFactoryInitialization;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.ContextAnnotationAutowireCandidateResolver;
/**
* A {@link BeanFactoryContribution} that configures the low-level
* infrastructure necessary to process an AOT context.
*
* @author Stephane Nicoll
*/
class InfrastructureContribution implements BeanFactoryContribution {
@Override
public void applyTo(BeanFactoryInitialization initialization) {
initialization.contribute(code -> {
code.add("// infrastructure\n");
code.addStatement("$T beanFactory = context.getDefaultListableBeanFactory()",
DefaultListableBeanFactory.class);
code.addStatement("beanFactory.setAutowireCandidateResolver(new $T())",
ContextAnnotationAutowireCandidateResolver.class);
});
}
}

View File

@@ -0,0 +1,10 @@
/**
* Support for generating code that represents the state of an application
* context.
*/
@NonNullApi
@NonNullFields
package org.springframework.context.generator;
import org.springframework.lang.NonNullApi;
import org.springframework.lang.NonNullFields;