Add support for refreshing a GenericApplicationContext for AOT

This commit adds a way to refresh a GenericApplicationContext for ahead
of time processing: refreshForAotProcessing() processes the bean factory
up to a point where it is about to create bean instances.

MergedBeanDefinitionPostProcessor implementations are the only bean
post processors that are invoked during this phase.

Closes gh-28065
This commit is contained in:
Stephane Nicoll
2022-03-06 18:10:31 +01:00
parent 9ba927215e
commit b5695b9248
4 changed files with 341 additions and 4 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* 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.
@@ -30,7 +30,9 @@ import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionCustomizer;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.Resource;
@@ -60,6 +62,10 @@ import org.springframework.util.Assert;
* this context is available right from the start, to be able to register bean
* definitions on it. {@link #refresh()} may only be called once.
*
* <p>This ApplicationContext implementation is suitable for Ahead of Time
* processing, using {@link #refreshForAotProcessing()} as an alternative to the
* regular {@link #refresh()}.
*
* <p>Usage example:
*
* <pre class="code">
@@ -86,6 +92,7 @@ import org.springframework.util.Assert;
*
* @author Juergen Hoeller
* @author Chris Beams
* @author Stephane Nicoll
* @since 1.1.2
* @see #registerBeanDefinition
* @see #refresh()
@@ -361,6 +368,34 @@ public class GenericApplicationContext extends AbstractApplicationContext implem
}
//---------------------------------------------------------------------
// AOT processing
//---------------------------------------------------------------------
/**
* Load or refresh the persistent representation of the configuration up to
* a point where the underlying bean factory is ready to create bean
* instances.
* <p>This variant of {@link #refresh()} is used by Ahead of Time processing
* that optimizes the application context, typically at build-time.
* <p>In this mode, only {@link BeanDefinitionRegistryPostProcessor} and
* {@link MergedBeanDefinitionPostProcessor} are invoked.
* @throws BeansException if the bean factory could not be initialized
* @throws IllegalStateException if already initialized and multiple refresh
* attempts are not supported
*/
public void refreshForAotProcessing() {
if (logger.isDebugEnabled()) {
logger.debug("Preparing bean factory for AOT processing");
}
prepareRefresh();
obtainFreshBeanFactory();
prepareBeanFactory(this.beanFactory);
postProcessBeanFactory(this.beanFactory);
invokeBeanFactoryPostProcessors(this.beanFactory);
PostProcessorRegistrationDelegate.invokeMergedBeanDefinitionPostProcessors(this.beanFactory);
}
//---------------------------------------------------------------------
// Convenient methods for registering individual beans
//---------------------------------------------------------------------

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* 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.
@@ -22,19 +22,25 @@ import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.BiConsumer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.PropertyValue;
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.config.ConstructorArgumentValues.ValueHolder;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.BeanDefinitionValueResolver;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.OrderComparator;
import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered;
@@ -47,6 +53,7 @@ import org.springframework.lang.Nullable;
*
* @author Juergen Hoeller
* @author Sam Brannen
* @author Stephane Nicoll
* @since 4.0
*/
final class PostProcessorRegistrationDelegate {
@@ -280,6 +287,36 @@ final class PostProcessorRegistrationDelegate {
beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(applicationContext));
}
/**
* Load and sort the post-processors of the specified type.
* @param beanFactory the bean factory to use
* @param beanPostProcessorType the post-processor type
* @param <T> the post-processor type
* @return a list of sorted post-processors for the specified type
*/
static <T extends BeanPostProcessor> List<T> loadBeanPostProcessors(
ConfigurableListableBeanFactory beanFactory, Class<T> beanPostProcessorType) {
String[] postProcessorNames = beanFactory.getBeanNamesForType(beanPostProcessorType, true, false);
List<T> postProcessors = new ArrayList<>();
for (String ppName : postProcessorNames) {
postProcessors.add(beanFactory.getBean(ppName, beanPostProcessorType));
}
sortPostProcessors(postProcessors, beanFactory);
return postProcessors;
}
/**
* Selectively invoke {@link MergedBeanDefinitionPostProcessor} instances
* registered in the specified bean factory, resolving bean definitions as
* well as any inner bean definitions that they may contain.
* @param beanFactory the bean factory to use
*/
static void invokeMergedBeanDefinitionPostProcessors(DefaultListableBeanFactory beanFactory) {
new MergedBeanDefinitionPostProcessorInvoker(beanFactory).invokeMergedBeanDefinitionPostProcessors();
}
private static void sortPostProcessors(List<?> postProcessors, ConfigurableListableBeanFactory beanFactory) {
// Nothing to sort?
if (postProcessors.size() <= 1) {
@@ -386,4 +423,65 @@ final class PostProcessorRegistrationDelegate {
}
}
private static final class MergedBeanDefinitionPostProcessorInvoker {
private final DefaultListableBeanFactory beanFactory;
private MergedBeanDefinitionPostProcessorInvoker(DefaultListableBeanFactory beanFactory) {
this.beanFactory = beanFactory;
}
private void invokeMergedBeanDefinitionPostProcessors() {
List<MergedBeanDefinitionPostProcessor> postProcessors = PostProcessorRegistrationDelegate.loadBeanPostProcessors(
this.beanFactory, MergedBeanDefinitionPostProcessor.class);
for (String beanName : this.beanFactory.getBeanDefinitionNames()) {
RootBeanDefinition bd = (RootBeanDefinition) this.beanFactory.getMergedBeanDefinition(beanName);
Class<?> beanType = resolveBeanType(bd);
postProcessRootBeanDefinition(postProcessors, beanName, beanType, bd);
}
}
private void postProcessRootBeanDefinition(List<MergedBeanDefinitionPostProcessor> postProcessors,
String beanName, Class<?> beanType, RootBeanDefinition bd) {
BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(this.beanFactory, beanName, bd);
postProcessors.forEach(postProcessor -> postProcessor.postProcessMergedBeanDefinition(bd, beanType, beanName));
for (PropertyValue propertyValue : bd.getPropertyValues().getPropertyValueList()) {
Object value = propertyValue.getValue();
if (value instanceof AbstractBeanDefinition innerBd) {
Class<?> innerBeanType = resolveBeanType(innerBd);
resolveInnerBeanDefinition(valueResolver, innerBd, (innerBeanName, innerBeanDefinition)
-> postProcessRootBeanDefinition(postProcessors, innerBeanName, innerBeanType, innerBeanDefinition));
}
}
for (ValueHolder valueHolder : bd.getConstructorArgumentValues().getIndexedArgumentValues().values()) {
Object value = valueHolder.getValue();
if (value instanceof AbstractBeanDefinition innerBd) {
Class<?> innerBeanType = resolveBeanType(innerBd);
resolveInnerBeanDefinition(valueResolver, innerBd, (innerBeanName, innerBeanDefinition)
-> postProcessRootBeanDefinition(postProcessors, innerBeanName, innerBeanType, innerBeanDefinition));
}
}
}
private void resolveInnerBeanDefinition(BeanDefinitionValueResolver valueResolver, BeanDefinition innerBeanDefinition,
BiConsumer<String, RootBeanDefinition> resolver) {
valueResolver.resolveInnerBean(null, innerBeanDefinition, (name, rbd) -> {
resolver.accept(name, rbd);
return Void.class;
});
}
private Class<?> resolveBeanType(AbstractBeanDefinition bd) {
if (!bd.hasBeanClass()) {
try {
bd.resolveBeanClass(this.beanFactory.getBeanClassLoader());
}
catch (ClassNotFoundException ex) {
// ignore
}
}
return bd.getResolvableType().toClass();
}
}
}