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

@@ -20,8 +20,11 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.core.io.support.SpringFactoriesLoader;
@@ -69,6 +72,16 @@ public class BeanDefinitionsContribution implements BeanFactoryContribution {
writeBeanDefinitions(initialization);
}
@Override
public BiPredicate<String, BeanDefinition> getBeanDefinitionExcludeFilter() {
List<BiPredicate<String, BeanDefinition>> predicates = new ArrayList<>();
for (String beanName : this.beanFactory.getBeanDefinitionNames()) {
handleMergedBeanDefinition(beanName, beanDefinition -> predicates.add(
getBeanRegistrationContribution(beanName, beanDefinition).getBeanDefinitionExcludeFilter()));
}
return predicates.stream().filter(Objects::nonNull).reduce((n, d) -> false, BiPredicate::or);
}
private void writeBeanDefinitions(BeanFactoryInitialization initialization) {
for (String beanName : this.beanFactory.getBeanDefinitionNames()) {
handleMergedBeanDefinition(beanName, beanDefinition -> {

View File

@@ -16,6 +16,10 @@
package org.springframework.beans.factory.generator;
import java.util.function.BiPredicate;
import org.springframework.beans.factory.config.BeanDefinition;
/**
* Contribute optimizations ahead of time to initialize a bean factory.
*
@@ -31,4 +35,14 @@ public interface BeanFactoryContribution {
*/
void applyTo(BeanFactoryInitialization initialization);
/**
* Return a predicate that determines if a particular bean definition
* should be excluded from processing. Can be used to exclude infrastructure
* that has been optimized using generated code.
* @return the predicate to use
*/
default BiPredicate<String, BeanDefinition> getBeanDefinitionExcludeFilter() {
return (beanName, beanDefinition) -> false;
}
}

View File

@@ -17,6 +17,7 @@
package org.springframework.beans.factory.generator;
import java.util.List;
import java.util.function.BiPredicate;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentMatchers;
@@ -26,6 +27,7 @@ import org.mockito.Mockito;
import org.springframework.aot.generator.DefaultGeneratedTypeContext;
import org.springframework.aot.generator.GeneratedType;
import org.springframework.aot.generator.GeneratedTypeContext;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;
@@ -90,6 +92,37 @@ class BeanDefinitionsContributionTests {
""");
}
@Test
void getBeanDefinitionWithNoUnderlyingContributorReturnFalseByDefault() {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
BiPredicate<String, BeanDefinition> excludeFilter = new BeanDefinitionsContribution(beanFactory)
.getBeanDefinitionExcludeFilter();
assertThat(excludeFilter.test("foo", new RootBeanDefinition())).isFalse();
}
@Test
@SuppressWarnings("unchecked")
void getBeanDefinitionExcludeFilterWrapsUnderlyingFilter() {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.registerBeanDefinition("bean1", new RootBeanDefinition());
beanFactory.registerBeanDefinition("bean2", new RootBeanDefinition());
BiPredicate<String, BeanDefinition> excludeFilter1 = Mockito.mock(BiPredicate.class);
BDDMockito.given(excludeFilter1.test(ArgumentMatchers.eq("bean1"), ArgumentMatchers.any(BeanDefinition.class))).willReturn(Boolean.TRUE);
BDDMockito.given(excludeFilter1.test(ArgumentMatchers.eq("bean2"), ArgumentMatchers.any(BeanDefinition.class))).willReturn(Boolean.FALSE);
BiPredicate<String, BeanDefinition> excludeFilter2 = Mockito.mock(BiPredicate.class);
BDDMockito.given(excludeFilter2.test(ArgumentMatchers.eq("bean2"), ArgumentMatchers.any(BeanDefinition.class))).willReturn(Boolean.TRUE);
BiPredicate<String, BeanDefinition> excludeFilter = new BeanDefinitionsContribution(beanFactory, List.of(
new TestBeanRegistrationContributionProvider("bean1", mockExcludeFilter(excludeFilter1)),
new TestBeanRegistrationContributionProvider("bean2", mockExcludeFilter(excludeFilter2)))
).getBeanDefinitionExcludeFilter();
assertThat(excludeFilter.test("bean2", new RootBeanDefinition())).isTrue();
Mockito.verify(excludeFilter1).test(ArgumentMatchers.eq("bean2"), ArgumentMatchers.any(BeanDefinition.class));
Mockito.verify(excludeFilter2).test(ArgumentMatchers.eq("bean2"), ArgumentMatchers.any(BeanDefinition.class));
assertThat(excludeFilter.test("bean1", new RootBeanDefinition())).isTrue();
Mockito.verify(excludeFilter1).test(ArgumentMatchers.eq("bean1"), ArgumentMatchers.any(BeanDefinition.class));
Mockito.verifyNoMoreInteractions(excludeFilter2);
}
private CodeSnippet contribute(DefaultListableBeanFactory beanFactory, GeneratedTypeContext generationContext) {
BeanDefinitionsContribution contribution = new BeanDefinitionsContribution(beanFactory);
BeanFactoryInitialization initialization = new BeanFactoryInitialization(generationContext);
@@ -102,6 +135,12 @@ class BeanDefinitionsContributionTests {
GeneratedType.of(ClassName.get(packageName, "Test")));
}
private BeanFactoryContribution mockExcludeFilter(BiPredicate<String, BeanDefinition> excludeFilter) {
BeanFactoryContribution contribution = Mockito.mock(BeanFactoryContribution.class);
BDDMockito.given(contribution.getBeanDefinitionExcludeFilter()).willReturn(excludeFilter);
return contribution;
}
static class TestBeanRegistrationContributionProvider implements BeanRegistrationContributionProvider {
private final String beanName;