Skip to content
This repository was archived by the owner on Feb 23, 2023. It is now read-only.

Commit 1d055a1

Browse files
committed
Handle ImportAware callbacks consistently
This commit refines our strategy to invoke ImportAware callbacks to use framework's ImportRegistry rather than running our own logic. At build time, we know requested the importing class of each bean that is import aware, and store that in a registry of our own. This is latter used to write the links that ImportAwareInvoker uses at build time. ImportAwareInvoker has also been refined to not invoke the callback if no import class is registered. Previously, it was throwing an innacurate exception as this is possible for an import aware configuration class to be processed directly (i.e. without a corresponding import arrangement). Closes gh-1079
1 parent cb5cec4 commit 1d055a1

File tree

10 files changed

+221
-246
lines changed

10 files changed

+221
-246
lines changed

spring-aot/src/main/java/org/springframework/context/annotation/BuildTimeBeanDefinitionsRegistrar.java

+50-6
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616

1717
package org.springframework.context.annotation;
1818

19+
import java.util.LinkedHashMap;
1920
import java.util.List;
21+
import java.util.Map;
2022

2123
import org.apache.commons.logging.Log;
2224
import org.apache.commons.logging.LogFactory;
@@ -26,7 +28,9 @@
2628
import org.springframework.beans.factory.support.RootBeanDefinition;
2729
import org.springframework.context.support.GenericApplicationContext;
2830
import org.springframework.core.io.support.SpringFactoriesLoader;
31+
import org.springframework.core.type.AnnotationMetadata;
2932
import org.springframework.util.Assert;
33+
import org.springframework.util.ClassUtils;
3034

3135
/**
3236
* Parse the {@link Configuration @Configuration classes} and provide the bean definitions
@@ -53,13 +57,9 @@ public ConfigurableListableBeanFactory processBeanDefinitions(GenericApplication
5357
}
5458
parseConfigurationClasses(context);
5559
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
56-
if (logger.isDebugEnabled()) {
57-
logger.debug("Resolving types for " + beanFactory.getBeanDefinitionCount() + " bean definitions");
58-
}
59-
for (String name : beanFactory.getBeanDefinitionNames()) {
60-
beanFactory.getType(name);
61-
}
60+
resolveBeanDefinitionTypes(beanFactory);
6261
postProcessBeanDefinitions(beanFactory);
62+
registerImportOriginRegistryIfNecessary(beanFactory);
6363
return beanFactory;
6464
}
6565

@@ -72,6 +72,15 @@ private void parseConfigurationClasses(GenericApplicationContext context) {
7272
configurationClassPostProcessor.postProcessBeanFactory(context.getBeanFactory());
7373
}
7474

75+
private void resolveBeanDefinitionTypes(ConfigurableListableBeanFactory beanFactory) {
76+
if (logger.isDebugEnabled()) {
77+
logger.debug("Resolving types for " + beanFactory.getBeanDefinitionCount() + " bean definitions");
78+
}
79+
for (String name : beanFactory.getBeanDefinitionNames()) {
80+
beanFactory.getType(name);
81+
}
82+
}
83+
7584
private void postProcessBeanDefinitions(ConfigurableListableBeanFactory beanFactory) {
7685
if (logger.isDebugEnabled()) {
7786
logger.debug("Post processing " + beanFactory.getBeanDefinitionCount() + " bean definitions");
@@ -86,4 +95,39 @@ private void postProcessBeanDefinitions(ConfigurableListableBeanFactory beanFact
8695
}
8796
}
8897

98+
private void registerImportOriginRegistryIfNecessary(ConfigurableListableBeanFactory beanFactory) {
99+
if (logger.isDebugEnabled()) {
100+
logger.debug("Retrieving import origins if necessary");
101+
}
102+
String importRegistryBeanName = ConfigurationClassPostProcessor.class.getName() + ".importRegistry";
103+
if (!beanFactory.containsBean(importRegistryBeanName)) {
104+
// No configuration classes were processed
105+
return;
106+
}
107+
ImportRegistry importRegistry = beanFactory.getBean(importRegistryBeanName, ImportRegistry.class);
108+
Map<String, Class<?>> origins = new LinkedHashMap<>();
109+
for (String name : beanFactory.getBeanDefinitionNames()) {
110+
Class<?> beanType = beanFactory.getType(name);
111+
if (beanType != null && ImportAware.class.isAssignableFrom(beanType)) {
112+
String type = ClassUtils.getUserClass(beanType).getName();
113+
AnnotationMetadata importingClassMetadata = importRegistry.getImportingClassFor(type);
114+
if (importingClassMetadata != null) {
115+
Class<?> importingClass = loadClass(importingClassMetadata.getClassName(),
116+
beanFactory.getBeanClassLoader());
117+
origins.put(type, importingClass);
118+
}
119+
}
120+
}
121+
ImportOriginRegistry.register(beanFactory, origins);
122+
}
123+
124+
private Class<?> loadClass(String beanClassName, ClassLoader classLoader) {
125+
try {
126+
return ClassUtils.forName(beanClassName, classLoader);
127+
}
128+
catch (ClassNotFoundException ex) {
129+
throw new IllegalStateException("Failed to load class " + beanClassName);
130+
}
131+
}
132+
89133
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package org.springframework.context.annotation;
2+
3+
import java.util.Collections;
4+
import java.util.Map;
5+
6+
import org.springframework.beans.factory.BeanFactory;
7+
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
8+
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
9+
import org.springframework.lang.Nullable;
10+
11+
/**
12+
* Keep track of import origin of {@link ImportAware} beans.
13+
*
14+
* @author Stephane Nicoll
15+
*/
16+
public final class ImportOriginRegistry {
17+
18+
private static final String BEAN_NAME = ImportOriginRegistry.class.getName();
19+
20+
private final Map<String, Class<?>> importOrigins;
21+
22+
ImportOriginRegistry(Map<String, Class<?>> importOrigins) {
23+
this.importOrigins = Collections.unmodifiableMap(importOrigins);
24+
}
25+
26+
/**
27+
* Register a registry on the specified bean factory using the specified
28+
* {@code importOrigins}.
29+
* @param beanFactory the bean factory to use to register a registry
30+
* @param importOrigins the import origins
31+
*/
32+
public static void register(ConfigurableBeanFactory beanFactory, Map<String, Class<?>> importOrigins) {
33+
beanFactory.registerSingleton(BEAN_NAME, new ImportOriginRegistry(importOrigins));
34+
}
35+
36+
/**
37+
* Return the registry instance associated with this registry, or {@code null} if none
38+
* is associated.
39+
* @param beanFactory the bean factory
40+
* @return the registry associated with this bean factory, or {@code null}
41+
*/
42+
@Nullable
43+
public static ImportOriginRegistry get(BeanFactory beanFactory) {
44+
try {
45+
return beanFactory.getBean(BEAN_NAME, ImportOriginRegistry.class);
46+
}
47+
catch (NoSuchBeanDefinitionException ex) {
48+
return null;
49+
}
50+
}
51+
52+
/**
53+
* Return an immutable mapping of the import origins.
54+
* @return a mapping between an import aware type and the class that imported it
55+
*/
56+
public Map<String, Class<?>> getImportOrigins() {
57+
return this.importOrigins;
58+
}
59+
60+
}

spring-aot/src/main/java/org/springframework/context/bootstrap/generator/infrastructure/BootstrapInfrastructureWriter.java

+12-13
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,10 @@
3434
import org.springframework.aot.context.annotation.InitDestroyBeanPostProcessor;
3535
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
3636
import org.springframework.context.annotation.ContextAnnotationAutowireCandidateResolver;
37+
import org.springframework.context.annotation.ImportOriginRegistry;
3738
import org.springframework.context.bootstrap.generator.bean.support.ParameterWriter;
38-
import org.springframework.context.origin.BeanFactoryStructure;
39-
import org.springframework.context.origin.BeanFactoryStructureAnalyzer;
39+
import org.springframework.context.bootstrap.generator.infrastructure.nativex.NativeConfigurationRegistry.ResourcesConfiguration;
40+
import org.springframework.context.bootstrap.generator.infrastructure.nativex.NativeResourcesEntry;
4041
import org.springframework.core.ResolvableType;
4142

4243
/**
@@ -75,14 +76,18 @@ public void writeInfrastructure(CodeBlock.Builder code) {
7576
}
7677

7778
private MethodSpec handleImportAwareInvoker() {
78-
BeanFactoryStructure structure = createBeanFactoryStructure();
79-
Map<String, Class<?>> importLinks = new ImportAwareLinksDiscoverer(structure, this.beanFactory.getBeanClassLoader())
80-
.buildImportAwareLinks(writerContext.getNativeConfigurationRegistry());
81-
if (importLinks.isEmpty()) {
79+
ImportOriginRegistry importOriginRegistry = ImportOriginRegistry.get(this.beanFactory);
80+
if (importOriginRegistry == null) {
8281
return null;
8382
}
83+
Map<String, Class<?>> importLinks = importOriginRegistry.getImportOrigins();
84+
ResourcesConfiguration resourcesConfiguration = this.writerContext.getNativeConfigurationRegistry().resources();
85+
importLinks.values().forEach((importingClass) -> resourcesConfiguration.add(
86+
NativeResourcesEntry.ofClass(importingClass)));
87+
8488
Builder code = CodeBlock.builder();
85-
code.addStatement("$T mappings = new $T<>()", ParameterizedTypeName.get(Map.class, String.class, String.class), LinkedHashMap.class);
89+
code.addStatement("$T mappings = new $T<>()", ParameterizedTypeName.get(Map.class, String.class, String.class),
90+
LinkedHashMap.class);
8691
importLinks.forEach((key, value) -> {
8792
code.addStatement("mappings.put($S, $S)", key, value.getName());
8893
});
@@ -117,10 +122,4 @@ private void writeLifecycleMethods(Builder code, Map<String, List<Method>> lifec
117122
});
118123
}
119124

120-
private BeanFactoryStructure createBeanFactoryStructure() {
121-
BeanFactoryStructureAnalyzer analyzer = new BeanFactoryStructureAnalyzer(
122-
this.beanFactory.getBeanClassLoader());
123-
return analyzer.analyze(this.beanFactory);
124-
}
125-
126125
}

spring-aot/src/main/java/org/springframework/context/bootstrap/generator/infrastructure/ImportAwareLinksDiscoverer.java

-88
This file was deleted.

spring-aot/src/test/java/org/springframework/context/annotation/BuildTimeBeanDefinitionsRegistrarTests.java

+45-1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
import org.springframework.mock.env.MockEnvironment;
4747

4848
import static org.assertj.core.api.Assertions.assertThat;
49+
import static org.assertj.core.api.Assertions.entry;
4950

5051
/**
5152
* Tests for {@link BuildTimeBeanDefinitionsRegistrar}.
@@ -98,7 +99,6 @@ void processBeanDefinitionsWithConditionsOnConfigurationClassMatching() {
9899
}
99100

100101
@Test
101-
// gh-953
102102
void processBeanDefinitionsWithCustomClasspathScanningAndNullBeanRegistry() {
103103
GenericApplicationContext context = createApplicationContext(GenericApplicationContext::new,
104104
new MockEnvironment().withProperty("test.one.enabled", "true"), CustomClasspathScanningConfiguration.class);
@@ -143,6 +143,50 @@ void postProcessBeanDefinitionsInvokeBeanFactoryAwareOnBeanDefinitionPostProcess
143143
.getAttribute("beanFactory")).isEqualTo(beanFactory);
144144
}
145145

146+
// Import Aware
147+
148+
@Test
149+
void processContextWithoutConfigurationClassesDoesNotCreateImportOriginRegistry() {
150+
GenericApplicationContext context = createApplicationContext(GenericApplicationContext::new);
151+
ConfigurableListableBeanFactory beanFactory = this.registrar.processBeanDefinitions(context);
152+
assertThat(ImportOriginRegistry.get(beanFactory)).isNull();
153+
}
154+
155+
@Test
156+
void processContextWithNoInstanceAwareConfigurationClassesCreateEmptyImportOriginRegistry() {
157+
GenericApplicationContext context = createApplicationContext(GenericApplicationContext::new, ConfigurationOne.class);
158+
ConfigurableListableBeanFactory beanFactory = this.registrar.processBeanDefinitions(context);
159+
ImportOriginRegistry registry = ImportOriginRegistry.get(beanFactory);
160+
assertThat(registry).isNotNull();
161+
assertThat(registry.getImportOrigins()).isEmpty();
162+
}
163+
164+
@Test
165+
void processContextWithInstanceAwareConfigurationClassImportedCreateMatchingImportOriginRegistry() {
166+
GenericApplicationContext context = createApplicationContext(GenericApplicationContext::new, ImportConfiguration.class);
167+
ConfigurableListableBeanFactory beanFactory = this.registrar.processBeanDefinitions(context);
168+
ImportOriginRegistry registry = ImportOriginRegistry.get(beanFactory);
169+
assertThat(registry).isNotNull();
170+
assertThat(registry.getImportOrigins()).containsOnly(entry(ConfigurationTwo.class.getName(),
171+
ImportConfiguration.class));
172+
}
173+
174+
@Test
175+
void processContextWithInstanceAwareConfigurationClassNotImportedCreateMatchingImportOriginRegistry() {
176+
GenericApplicationContext context = createApplicationContext(GenericApplicationContext::new, ConfigurationTwo.class);
177+
ConfigurableListableBeanFactory beanFactory = this.registrar.processBeanDefinitions(context);
178+
ImportOriginRegistry registry = ImportOriginRegistry.get(beanFactory);
179+
assertThat(registry).isNotNull();
180+
assertThat(registry.getImportOrigins()).isEmpty();
181+
}
182+
183+
@Test
184+
void processContextWithNullBeanDefinitionTypeIsIgnoredByImportOriginRegistry() {
185+
GenericApplicationContext context = createApplicationContext(GenericApplicationContext::new);
186+
context.registerBeanDefinition("test", new RootBeanDefinition());
187+
ConfigurableListableBeanFactory beanFactory = this.registrar.processBeanDefinitions(context);
188+
assertThat(ImportOriginRegistry.get(beanFactory)).isNull();
189+
}
146190

147191
private <T extends GenericApplicationContext> T createApplicationContext(
148192
Supplier<T> contextFactory, Class<?>... componentClasses) {

spring-aot/src/test/java/org/springframework/context/annotation/samples/simple/ConfigurationTwo.java

+8-2
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,19 @@
1818

1919
import org.springframework.context.annotation.Bean;
2020
import org.springframework.context.annotation.Configuration;
21+
import org.springframework.context.annotation.ImportAware;
22+
import org.springframework.core.type.AnnotationMetadata;
2123

2224
@Configuration(proxyBeanMethods = false)
23-
public class ConfigurationTwo {
25+
public class ConfigurationTwo implements ImportAware {
26+
27+
@Override
28+
public void setImportMetadata(AnnotationMetadata importMetadata) {
29+
30+
}
2431

2532
@Bean
2633
String beanTwo() {
2734
return "two";
2835
}
29-
3036
}

0 commit comments

Comments
 (0)