Skip to content

Commit b578ab0

Browse files
committed
Add support for DeferredImportSelector
Add DeferredImportSelector interface that can be used to select imports after all @configuration beans have been processed.
1 parent 40458b1 commit b578ab0

File tree

5 files changed

+263
-17
lines changed

5 files changed

+263
-17
lines changed

spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java

Lines changed: 77 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import java.util.HashSet;
2828
import java.util.Iterator;
2929
import java.util.LinkedHashSet;
30+
import java.util.LinkedList;
3031
import java.util.List;
3132
import java.util.Map;
3233
import java.util.Set;
@@ -37,18 +38,22 @@
3738
import org.springframework.beans.BeanUtils;
3839
import org.springframework.beans.factory.Aware;
3940
import org.springframework.beans.factory.BeanClassLoaderAware;
41+
import org.springframework.beans.factory.BeanDefinitionStoreException;
4042
import org.springframework.beans.factory.BeanFactory;
4143
import org.springframework.beans.factory.BeanFactoryAware;
44+
import org.springframework.beans.factory.config.BeanDefinition;
4245
import org.springframework.beans.factory.config.BeanDefinitionHolder;
4346
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
4447
import org.springframework.beans.factory.parsing.Location;
4548
import org.springframework.beans.factory.parsing.Problem;
4649
import org.springframework.beans.factory.parsing.ProblemReporter;
50+
import org.springframework.beans.factory.support.AbstractBeanDefinition;
4751
import org.springframework.beans.factory.support.BeanDefinitionReader;
4852
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
4953
import org.springframework.beans.factory.support.BeanNameGenerator;
5054
import org.springframework.context.ResourceLoaderAware;
5155
import org.springframework.core.annotation.AnnotationAttributes;
56+
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
5257
import org.springframework.core.env.CompositePropertySource;
5358
import org.springframework.core.env.Environment;
5459
import org.springframework.core.env.PropertySource;
@@ -86,6 +91,15 @@
8691
*/
8792
class ConfigurationClassParser {
8893

94+
private static final Comparator<DeferredImportSelectorHolder> DEFERRED_IMPORT_COMPARATOR =
95+
new Comparator<ConfigurationClassParser.DeferredImportSelectorHolder>() {
96+
public int compare(DeferredImportSelectorHolder o1,
97+
DeferredImportSelectorHolder o2) {
98+
return AnnotationAwareOrderComparator.INSTANCE.compare(
99+
o1.getImportSelector(), o2.getImportSelector());
100+
}
101+
};
102+
89103
protected final Log logger = LogFactory.getLog(getClass());
90104

91105
private final MetadataReaderFactory metadataReaderFactory;
@@ -112,6 +126,8 @@ class ConfigurationClassParser {
112126

113127
private final BeanNameGenerator beanNameGenerator;
114128

129+
private final List<DeferredImportSelectorHolder> deferredImportSelectors =
130+
new LinkedList<DeferredImportSelectorHolder>();
115131

116132
/**
117133
* Create a new {@link ConfigurationClassParser} instance that will be used
@@ -131,14 +147,31 @@ public ConfigurationClassParser(MetadataReaderFactory metadataReaderFactory,
131147
resourceLoader, environment, componentScanBeanNameGenerator, registry);
132148
}
133149

150+
public void parse(Set<BeanDefinitionHolder> configCandidates) {
151+
for (BeanDefinitionHolder holder : configCandidates) {
152+
BeanDefinition bd = holder.getBeanDefinition();
153+
try {
154+
if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
155+
parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
156+
}
157+
else {
158+
parse(bd.getBeanClassName(), holder.getBeanName());
159+
}
160+
}
161+
catch (IOException ex) {
162+
throw new BeanDefinitionStoreException("Failed to load bean class: " + bd.getBeanClassName(), ex);
163+
}
164+
}
165+
processDeferredImportSelectors();
166+
}
134167

135168
/**
136169
* Parse the specified {@link Configuration @Configuration} class.
137170
* @param className the name of the class to parse
138171
* @param beanName may be null, but if populated represents the bean id
139172
* (assumes that this configuration class was configured via XML)
140173
*/
141-
public void parse(String className, String beanName) throws IOException {
174+
protected void parse(String className, String beanName) throws IOException {
142175
ConfigurationMetadataReader reader = new ConfigurationMetadataReader(className);
143176
processConfigurationClass(reader.getConfigurationClass(beanName));
144177
}
@@ -148,7 +181,7 @@ public void parse(String className, String beanName) throws IOException {
148181
* @param clazz the Class to parse
149182
* @param beanName must not be null (as of Spring 3.1.1)
150183
*/
151-
public void parse(Class<?> clazz, String beanName) throws IOException {
184+
protected void parse(Class<?> clazz, String beanName) throws IOException {
152185
processConfigurationClass(new ConfigurationClass(clazz, beanName));
153186
}
154187

@@ -323,6 +356,21 @@ private Set<String> getImports(String className, Set<String> imports, Set<String
323356
return imports;
324357
}
325358

359+
private void processDeferredImportSelectors() {
360+
Collections.sort(this.deferredImportSelectors, DEFERRED_IMPORT_COMPARATOR);
361+
for (DeferredImportSelectorHolder deferredImport : this.deferredImportSelectors) {
362+
try {
363+
ConfigurationClass configClass = deferredImport.getConfigurationClass();
364+
String[] imports = deferredImport.getImportSelector().selectImports(configClass.getMetadata());
365+
processImport(configClass, imports, false);
366+
}
367+
catch (IOException ex) {
368+
throw new BeanDefinitionStoreException("Failed to load bean class: ", ex);
369+
}
370+
}
371+
deferredImportSelectors.clear();
372+
}
373+
326374
private void processImport(ConfigurationClass configClass, String[] classesToImport, boolean checkForCircularImports) throws IOException {
327375
if (checkForCircularImports && this.importStack.contains(configClass)) {
328376
this.problemReporter.error(new CircularImportProblem(configClass, this.importStack, configClass.getMetadata()));
@@ -339,7 +387,12 @@ private void processImport(ConfigurationClass configClass, String[] classesToImp
339387
ImportSelector selector = BeanUtils.instantiateClass(
340388
this.resourceLoader.getClassLoader().loadClass(candidate), ImportSelector.class);
341389
invokeAwareMethods(selector);
342-
processImport(configClass, selector.selectImports(importingClassMetadata), false);
390+
if(selector instanceof DeferredImportSelector) {
391+
this.deferredImportSelectors.add(new DeferredImportSelectorHolder(
392+
configClass, (DeferredImportSelector) selector));
393+
} else {
394+
processImport(configClass, selector.selectImports(importingClassMetadata), false);
395+
}
343396
}
344397
catch (ClassNotFoundException ex) {
345398
throw new IllegalStateException(ex);
@@ -572,4 +625,25 @@ public void log() {
572625
logger.debug(message, t);
573626
}
574627
}
628+
629+
630+
private static class DeferredImportSelectorHolder {
631+
632+
private ConfigurationClass configurationClass;
633+
634+
private DeferredImportSelector importSelector;
635+
636+
public DeferredImportSelectorHolder(ConfigurationClass configurationClass, DeferredImportSelector importSelector) {
637+
this.configurationClass = configurationClass;
638+
this.importSelector = importSelector;
639+
}
640+
641+
public ConfigurationClass getConfigurationClass() {
642+
return configurationClass;
643+
}
644+
645+
public DeferredImportSelector getImportSelector() {
646+
return importSelector;
647+
}
648+
}
575649
}

spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -276,20 +276,7 @@ public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
276276
ConfigurationClassParser parser = new ConfigurationClassParser(
277277
this.metadataReaderFactory, this.problemReporter, this.environment,
278278
this.resourceLoader, this.componentScanBeanNameGenerator, registry);
279-
for (BeanDefinitionHolder holder : configCandidates) {
280-
BeanDefinition bd = holder.getBeanDefinition();
281-
try {
282-
if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
283-
parser.parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
284-
}
285-
else {
286-
parser.parse(bd.getBeanClassName(), holder.getBeanName());
287-
}
288-
}
289-
catch (IOException ex) {
290-
throw new BeanDefinitionStoreException("Failed to load bean class: " + bd.getBeanClassName(), ex);
291-
}
292-
}
279+
parser.parse(configCandidates);
293280
parser.validate();
294281

295282
// Handle any @PropertySource annotations
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Copyright 2002-2013 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.context.annotation;
18+
19+
/**
20+
* A variation of {@link ImportSelector} that runs after all {@code @Configuration} beans
21+
* have been processed. This type of selector can be particularly useful when the selected
22+
* imports are {@code @Conditional}.
23+
*
24+
* <p>Implementations can also extend the {@link org.springframework.core.Ordered}
25+
* interface or use the {@link org.springframework.core.annotation.Order} annotation to
26+
* indicate a precedence against other {@link DeferredImportSelector}s.
27+
*
28+
* @author Phillip Webb
29+
* @since 4.0
30+
*/
31+
public interface DeferredImportSelector extends ImportSelector {
32+
33+
}

spring-context/src/main/java/org/springframework/context/annotation/ImportSelector.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,14 @@
3232
* <li>{@link org.springframework.context.ResourceLoaderAware ResourceLoaderAware}</li>
3333
* </ul>
3434
*
35+
* <p>ImportSelectors are usually processed in the same way as regular {@code @Import}
36+
* annotations, however, it is also possible to defer selection of imports until all
37+
* {@code @Configuration} classes have been processed (see {@link DeferredImportSelector}
38+
* for details).
39+
*
3540
* @author Chris Beams
3641
* @since 3.1
42+
* @see DeferredImportSelector
3743
* @see Import
3844
* @see ImportBeanDefinitionRegistrar
3945
* @see Configuration
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
/*
2+
* Copyright 2002-2013 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.context.annotation;
18+
19+
import static org.mockito.Matchers.any;
20+
import static org.mockito.Matchers.eq;
21+
import static org.mockito.Mockito.inOrder;
22+
import static org.mockito.Mockito.spy;
23+
24+
import java.lang.annotation.ElementType;
25+
import java.lang.annotation.Retention;
26+
import java.lang.annotation.RetentionPolicy;
27+
import java.lang.annotation.Target;
28+
29+
import org.junit.Test;
30+
import org.mockito.InOrder;
31+
import org.springframework.beans.factory.config.BeanDefinition;
32+
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
33+
import org.springframework.core.Ordered;
34+
import org.springframework.core.annotation.Order;
35+
import org.springframework.core.type.AnnotationMetadata;
36+
37+
/**
38+
* Tests for {@link ImportSelector} and {@link DeferredImportSelector}.
39+
*
40+
* @author Phillip Webb
41+
*/
42+
public class ImportSelectorTests {
43+
44+
@Test
45+
public void importSelectors() {
46+
DefaultListableBeanFactory beanFactory = spy(new DefaultListableBeanFactory());
47+
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
48+
beanFactory);
49+
context.register(Config.class);
50+
context.refresh();
51+
context.getBean(Config.class);
52+
InOrder ordered = inOrder(beanFactory);
53+
ordered.verify(beanFactory).registerBeanDefinition(eq("a"), any(BeanDefinition.class));
54+
ordered.verify(beanFactory).registerBeanDefinition(eq("b"), any(BeanDefinition.class));
55+
ordered.verify(beanFactory).registerBeanDefinition(eq("d"), any(BeanDefinition.class));
56+
ordered.verify(beanFactory).registerBeanDefinition(eq("c"), any(BeanDefinition.class));
57+
}
58+
59+
@Sample
60+
@Configuration
61+
static class Config {
62+
}
63+
64+
@Target(ElementType.TYPE)
65+
@Retention(RetentionPolicy.RUNTIME)
66+
@Import({ DeferredImportSelector1.class, DeferredImportSelector2.class,
67+
ImportSelector1.class, ImportSelector2.class })
68+
public static @interface Sample {
69+
}
70+
71+
public static class ImportSelector1 implements ImportSelector {
72+
73+
@Override
74+
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
75+
return new String[] { ImportedSelector1.class.getName() };
76+
}
77+
}
78+
79+
public static class ImportSelector2 implements ImportSelector {
80+
81+
@Override
82+
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
83+
return new String[] { ImportedSelector2.class.getName() };
84+
}
85+
}
86+
87+
public static class DeferredImportSelector1 implements DeferredImportSelector, Ordered {
88+
89+
@Override
90+
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
91+
return new String[] { DeferredImportedSelector1.class.getName() };
92+
}
93+
94+
@Override
95+
public int getOrder() {
96+
return Ordered.LOWEST_PRECEDENCE;
97+
}
98+
}
99+
100+
@Order(Ordered.HIGHEST_PRECEDENCE)
101+
public static class DeferredImportSelector2 implements DeferredImportSelector {
102+
103+
@Override
104+
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
105+
return new String[] { DeferredImportedSelector2.class.getName() };
106+
}
107+
108+
}
109+
110+
@Configuration
111+
public static class ImportedSelector1 {
112+
113+
@Bean
114+
public String a() {
115+
return "a";
116+
}
117+
}
118+
119+
@Configuration
120+
public static class ImportedSelector2 {
121+
122+
@Bean
123+
public String b() {
124+
return "b";
125+
}
126+
}
127+
128+
@Configuration
129+
public static class DeferredImportedSelector1 {
130+
131+
@Bean
132+
public String c() {
133+
return "c";
134+
}
135+
}
136+
137+
@Configuration
138+
public static class DeferredImportedSelector2 {
139+
140+
@Bean
141+
public String d() {
142+
return "d";
143+
}
144+
}
145+
146+
}

0 commit comments

Comments
 (0)