Skip to content

Commit b5695b9

Browse files
committed
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
1 parent 9ba9272 commit b5695b9

File tree

4 files changed

+341
-4
lines changed

4 files changed

+341
-4
lines changed

spring-context/src/main/java/org/springframework/context/support/GenericApplicationContext.java

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -30,7 +30,9 @@
3030
import org.springframework.beans.factory.config.BeanDefinitionCustomizer;
3131
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
3232
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
33+
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
3334
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
35+
import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor;
3436
import org.springframework.beans.factory.support.RootBeanDefinition;
3537
import org.springframework.context.ApplicationContext;
3638
import org.springframework.core.io.Resource;
@@ -60,6 +62,10 @@
6062
* this context is available right from the start, to be able to register bean
6163
* definitions on it. {@link #refresh()} may only be called once.
6264
*
65+
* <p>This ApplicationContext implementation is suitable for Ahead of Time
66+
* processing, using {@link #refreshForAotProcessing()} as an alternative to the
67+
* regular {@link #refresh()}.
68+
*
6369
* <p>Usage example:
6470
*
6571
* <pre class="code">
@@ -86,6 +92,7 @@
8692
*
8793
* @author Juergen Hoeller
8894
* @author Chris Beams
95+
* @author Stephane Nicoll
8996
* @since 1.1.2
9097
* @see #registerBeanDefinition
9198
* @see #refresh()
@@ -361,6 +368,34 @@ public boolean isAlias(String beanName) {
361368
}
362369

363370

371+
//---------------------------------------------------------------------
372+
// AOT processing
373+
//---------------------------------------------------------------------
374+
375+
/**
376+
* Load or refresh the persistent representation of the configuration up to
377+
* a point where the underlying bean factory is ready to create bean
378+
* instances.
379+
* <p>This variant of {@link #refresh()} is used by Ahead of Time processing
380+
* that optimizes the application context, typically at build-time.
381+
* <p>In this mode, only {@link BeanDefinitionRegistryPostProcessor} and
382+
* {@link MergedBeanDefinitionPostProcessor} are invoked.
383+
* @throws BeansException if the bean factory could not be initialized
384+
* @throws IllegalStateException if already initialized and multiple refresh
385+
* attempts are not supported
386+
*/
387+
public void refreshForAotProcessing() {
388+
if (logger.isDebugEnabled()) {
389+
logger.debug("Preparing bean factory for AOT processing");
390+
}
391+
prepareRefresh();
392+
obtainFreshBeanFactory();
393+
prepareBeanFactory(this.beanFactory);
394+
postProcessBeanFactory(this.beanFactory);
395+
invokeBeanFactoryPostProcessors(this.beanFactory);
396+
PostProcessorRegistrationDelegate.invokeMergedBeanDefinitionPostProcessors(this.beanFactory);
397+
}
398+
364399
//---------------------------------------------------------------------
365400
// Convenient methods for registering individual beans
366401
//---------------------------------------------------------------------

spring-context/src/main/java/org/springframework/context/support/PostProcessorRegistrationDelegate.java

Lines changed: 99 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -22,19 +22,25 @@
2222
import java.util.HashSet;
2323
import java.util.List;
2424
import java.util.Set;
25+
import java.util.function.BiConsumer;
2526

2627
import org.apache.commons.logging.Log;
2728
import org.apache.commons.logging.LogFactory;
2829

30+
import org.springframework.beans.PropertyValue;
2931
import org.springframework.beans.factory.config.BeanDefinition;
3032
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
3133
import org.springframework.beans.factory.config.BeanPostProcessor;
3234
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
35+
import org.springframework.beans.factory.config.ConstructorArgumentValues.ValueHolder;
36+
import org.springframework.beans.factory.support.AbstractBeanDefinition;
3337
import org.springframework.beans.factory.support.AbstractBeanFactory;
3438
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
3539
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
40+
import org.springframework.beans.factory.support.BeanDefinitionValueResolver;
3641
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
3742
import org.springframework.beans.factory.support.MergedBeanDefinitionPostProcessor;
43+
import org.springframework.beans.factory.support.RootBeanDefinition;
3844
import org.springframework.core.OrderComparator;
3945
import org.springframework.core.Ordered;
4046
import org.springframework.core.PriorityOrdered;
@@ -47,6 +53,7 @@
4753
*
4854
* @author Juergen Hoeller
4955
* @author Sam Brannen
56+
* @author Stephane Nicoll
5057
* @since 4.0
5158
*/
5259
final class PostProcessorRegistrationDelegate {
@@ -280,6 +287,36 @@ else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {
280287
beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(applicationContext));
281288
}
282289

290+
/**
291+
* Load and sort the post-processors of the specified type.
292+
* @param beanFactory the bean factory to use
293+
* @param beanPostProcessorType the post-processor type
294+
* @param <T> the post-processor type
295+
* @return a list of sorted post-processors for the specified type
296+
*/
297+
static <T extends BeanPostProcessor> List<T> loadBeanPostProcessors(
298+
ConfigurableListableBeanFactory beanFactory, Class<T> beanPostProcessorType) {
299+
300+
String[] postProcessorNames = beanFactory.getBeanNamesForType(beanPostProcessorType, true, false);
301+
List<T> postProcessors = new ArrayList<>();
302+
for (String ppName : postProcessorNames) {
303+
postProcessors.add(beanFactory.getBean(ppName, beanPostProcessorType));
304+
}
305+
sortPostProcessors(postProcessors, beanFactory);
306+
return postProcessors;
307+
308+
}
309+
310+
/**
311+
* Selectively invoke {@link MergedBeanDefinitionPostProcessor} instances
312+
* registered in the specified bean factory, resolving bean definitions as
313+
* well as any inner bean definitions that they may contain.
314+
* @param beanFactory the bean factory to use
315+
*/
316+
static void invokeMergedBeanDefinitionPostProcessors(DefaultListableBeanFactory beanFactory) {
317+
new MergedBeanDefinitionPostProcessorInvoker(beanFactory).invokeMergedBeanDefinitionPostProcessors();
318+
}
319+
283320
private static void sortPostProcessors(List<?> postProcessors, ConfigurableListableBeanFactory beanFactory) {
284321
// Nothing to sort?
285322
if (postProcessors.size() <= 1) {
@@ -386,4 +423,65 @@ private boolean isInfrastructureBean(@Nullable String beanName) {
386423
}
387424
}
388425

426+
private static final class MergedBeanDefinitionPostProcessorInvoker {
427+
428+
private final DefaultListableBeanFactory beanFactory;
429+
430+
private MergedBeanDefinitionPostProcessorInvoker(DefaultListableBeanFactory beanFactory) {
431+
this.beanFactory = beanFactory;
432+
}
433+
434+
private void invokeMergedBeanDefinitionPostProcessors() {
435+
List<MergedBeanDefinitionPostProcessor> postProcessors = PostProcessorRegistrationDelegate.loadBeanPostProcessors(
436+
this.beanFactory, MergedBeanDefinitionPostProcessor.class);
437+
for (String beanName : this.beanFactory.getBeanDefinitionNames()) {
438+
RootBeanDefinition bd = (RootBeanDefinition) this.beanFactory.getMergedBeanDefinition(beanName);
439+
Class<?> beanType = resolveBeanType(bd);
440+
postProcessRootBeanDefinition(postProcessors, beanName, beanType, bd);
441+
}
442+
}
443+
444+
private void postProcessRootBeanDefinition(List<MergedBeanDefinitionPostProcessor> postProcessors,
445+
String beanName, Class<?> beanType, RootBeanDefinition bd) {
446+
BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(this.beanFactory, beanName, bd);
447+
postProcessors.forEach(postProcessor -> postProcessor.postProcessMergedBeanDefinition(bd, beanType, beanName));
448+
for (PropertyValue propertyValue : bd.getPropertyValues().getPropertyValueList()) {
449+
Object value = propertyValue.getValue();
450+
if (value instanceof AbstractBeanDefinition innerBd) {
451+
Class<?> innerBeanType = resolveBeanType(innerBd);
452+
resolveInnerBeanDefinition(valueResolver, innerBd, (innerBeanName, innerBeanDefinition)
453+
-> postProcessRootBeanDefinition(postProcessors, innerBeanName, innerBeanType, innerBeanDefinition));
454+
}
455+
}
456+
for (ValueHolder valueHolder : bd.getConstructorArgumentValues().getIndexedArgumentValues().values()) {
457+
Object value = valueHolder.getValue();
458+
if (value instanceof AbstractBeanDefinition innerBd) {
459+
Class<?> innerBeanType = resolveBeanType(innerBd);
460+
resolveInnerBeanDefinition(valueResolver, innerBd, (innerBeanName, innerBeanDefinition)
461+
-> postProcessRootBeanDefinition(postProcessors, innerBeanName, innerBeanType, innerBeanDefinition));
462+
}
463+
}
464+
}
465+
466+
private void resolveInnerBeanDefinition(BeanDefinitionValueResolver valueResolver, BeanDefinition innerBeanDefinition,
467+
BiConsumer<String, RootBeanDefinition> resolver) {
468+
valueResolver.resolveInnerBean(null, innerBeanDefinition, (name, rbd) -> {
469+
resolver.accept(name, rbd);
470+
return Void.class;
471+
});
472+
}
473+
474+
private Class<?> resolveBeanType(AbstractBeanDefinition bd) {
475+
if (!bd.hasBeanClass()) {
476+
try {
477+
bd.resolveBeanClass(this.beanFactory.getBeanClassLoader());
478+
}
479+
catch (ClassNotFoundException ex) {
480+
// ignore
481+
}
482+
}
483+
return bd.getResolvableType().toClass();
484+
}
485+
}
486+
389487
}

spring-context/src/test/java/org/springframework/context/annotation/AnnotationConfigApplicationContextTests.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -396,6 +396,15 @@ void individualBeanWithFactoryBeanObjectTypeAsTargetTypeAndLazy() {
396396
assertThat(context.getBeanNamesForType(TypedFactoryBean.class)).hasSize(1);
397397
}
398398

399+
@Test
400+
void refreshForAotProcessingWithConfiguration() {
401+
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
402+
context.register(Config.class);
403+
context.refreshForAotProcessing();
404+
assertThat(context.getBeanFactory().getBeanDefinitionNames()).contains(
405+
"annotationConfigApplicationContextTests.Config", "testBean");
406+
}
407+
399408

400409
@Configuration
401410
static class Config {

0 commit comments

Comments
 (0)