Skip to content

Commit ea9ad4e

Browse files
committed
Properly evaluate @conditional in case of multiple imports for same config class
Issue: SPR-11788 (cherry picked from commit 52f44b3)
1 parent 482eff8 commit ea9ad4e

File tree

5 files changed

+168
-31
lines changed

5 files changed

+168
-31
lines changed

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2013 the original author or authors.
2+
* Copyright 2002-2014 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.
@@ -83,13 +83,13 @@ public boolean shouldSkip(AnnotatedTypeMetadata metadata, ConfigurationPhase pha
8383

8484
for (String[] conditionClasses : getConditionClasses(metadata)) {
8585
for (String conditionClass : conditionClasses) {
86-
Condition condition = getCondition(conditionClass, context.getClassLoader());
86+
Condition condition = getCondition(conditionClass, this.context.getClassLoader());
8787
ConfigurationPhase requiredPhase = null;
8888
if (condition instanceof ConfigurationCondition) {
8989
requiredPhase = ((ConfigurationCondition) condition).getConfigurationPhase();
9090
}
9191
if (requiredPhase == null || requiredPhase == phase) {
92-
if (!condition.matches(context, metadata)) {
92+
if (!condition.matches(this.context, metadata)) {
9393
return true;
9494
}
9595
}

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

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2013 the original author or authors.
2+
* Copyright 2002-2014 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.
@@ -16,7 +16,6 @@
1616

1717
package org.springframework.context.annotation;
1818

19-
import java.util.Collections;
2019
import java.util.LinkedHashMap;
2120
import java.util.LinkedHashSet;
2221
import java.util.Map;
@@ -54,7 +53,7 @@ final class ConfigurationClass {
5453

5554
private String beanName;
5655

57-
private final ConfigurationClass importedBy;
56+
private final Set<ConfigurationClass> importedBy = new LinkedHashSet<ConfigurationClass>(1);
5857

5958
private final Set<BeanMethod> beanMethods = new LinkedHashSet<BeanMethod>();
6059

@@ -77,7 +76,6 @@ public ConfigurationClass(MetadataReader metadataReader, String beanName) {
7776
this.metadata = metadataReader.getAnnotationMetadata();
7877
this.resource = metadataReader.getResource();
7978
this.beanName = beanName;
80-
this.importedBy = null;
8179
}
8280

8381
/**
@@ -91,7 +89,7 @@ public ConfigurationClass(MetadataReader metadataReader, String beanName) {
9189
public ConfigurationClass(MetadataReader metadataReader, ConfigurationClass importedBy) {
9290
this.metadata = metadataReader.getAnnotationMetadata();
9391
this.resource = metadataReader.getResource();
94-
this.importedBy = importedBy;
92+
this.importedBy.add(importedBy);
9593
}
9694

9795
/**
@@ -106,7 +104,6 @@ public ConfigurationClass(Class<?> clazz, String beanName) {
106104
this.metadata = new StandardAnnotationMetadata(clazz, true);
107105
this.resource = new DescriptiveResource(clazz.toString());
108106
this.beanName = beanName;
109-
this.importedBy = null;
110107
}
111108

112109
/**
@@ -120,7 +117,7 @@ public ConfigurationClass(Class<?> clazz, String beanName) {
120117
public ConfigurationClass(Class<?> clazz, ConfigurationClass importedBy) {
121118
this.metadata = new StandardAnnotationMetadata(clazz, true);
122119
this.resource = new DescriptiveResource(clazz.toString());
123-
this.importedBy = importedBy;
120+
this.importedBy.add(importedBy);
124121
}
125122

126123

@@ -151,16 +148,24 @@ public String getBeanName() {
151148
* @see #getImportedBy()
152149
*/
153150
public boolean isImported() {
154-
return (this.importedBy != null);
151+
return !this.importedBy.isEmpty();
155152
}
156153

157154
/**
158-
* Return the configuration class that imported this class,
159-
* or {@code null} if this configuration was not imported.
160-
* @since 4.0
155+
* Merge the imported-by declarations from the given configuration class into this one.
156+
* @since 4.0.5
157+
*/
158+
public void mergeImportedBy(ConfigurationClass otherConfigClass) {
159+
this.importedBy.addAll(otherConfigClass.importedBy);
160+
}
161+
162+
/**
163+
* Return the configuration classes that imported this class,
164+
* or an empty Set if this configuration was not imported.
165+
* @since 4.0.5
161166
* @see #isImported()
162167
*/
163-
public ConfigurationClass getImportedBy() {
168+
public Set<ConfigurationClass> getImportedBy() {
164169
return this.importedBy;
165170
}
166171

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

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2013 the original author or authors.
2+
* Copyright 2002-2014 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.
@@ -391,14 +391,19 @@ public boolean shouldSkip(ConfigurationClass configClass) {
391391
Boolean skip = this.skipped.get(configClass);
392392
if (skip == null) {
393393
if (configClass.isImported()) {
394-
if (shouldSkip(configClass.getImportedBy())) {
395-
// The config that imported this one was skipped, therefore we are skipped
394+
boolean allSkipped = true;
395+
for (ConfigurationClass importedBy : configClass.getImportedBy()) {
396+
if (!shouldSkip(importedBy)) {
397+
allSkipped = false;
398+
}
399+
}
400+
if (allSkipped) {
401+
// The config classes that imported this one were all skipped, therefore we are skipped...
396402
skip = true;
397403
}
398404
}
399405
if (skip == null) {
400-
skip = conditionEvaluator.shouldSkip(configClass.getMetadata(),
401-
ConfigurationPhase.REGISTER_BEAN);
406+
skip = conditionEvaluator.shouldSkip(configClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN);
402407
}
403408
this.skipped.put(configClass, skip);
404409
}

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

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.util.Comparator;
2525
import java.util.HashMap;
2626
import java.util.Iterator;
27+
import java.util.LinkedHashMap;
2728
import java.util.LinkedHashSet;
2829
import java.util.LinkedList;
2930
import java.util.List;
@@ -112,7 +113,8 @@ public int compare(DeferredImportSelectorHolder o1, DeferredImportSelectorHolder
112113

113114
private final ComponentScanAnnotationParser componentScanParser;
114115

115-
private final Set<ConfigurationClass> configurationClasses = new LinkedHashSet<ConfigurationClass>();
116+
private final Map<ConfigurationClass, ConfigurationClass> configurationClasses =
117+
new LinkedHashMap<ConfigurationClass, ConfigurationClass>();
116118

117119
private final Map<String, ConfigurationClass> knownSuperclasses = new HashMap<String, ConfigurationClass>();
118120

@@ -189,13 +191,23 @@ protected void processConfigurationClass(ConfigurationClass configClass) throws
189191
return;
190192
}
191193

192-
if (this.configurationClasses.contains(configClass) && configClass.getBeanName() != null) {
193-
// Explicit bean definition found, probably replacing an import.
194-
// Let's remove the old one and go with the new one.
195-
this.configurationClasses.remove(configClass);
196-
for (Iterator<ConfigurationClass> it = this.knownSuperclasses.values().iterator(); it.hasNext();) {
197-
if (configClass.equals(it.next())) {
198-
it.remove();
194+
ConfigurationClass existingClass = this.configurationClasses.get(configClass);
195+
if (existingClass != null) {
196+
if (configClass.isImported()) {
197+
if (existingClass.isImported()) {
198+
existingClass.mergeImportedBy(configClass);
199+
}
200+
// Otherwise ignore new imported config class; existing non-imported class overrides it.
201+
return;
202+
}
203+
else {
204+
// Explicit bean definition found, probably replacing an import.
205+
// Let's remove the old one and go with the new one.
206+
this.configurationClasses.remove(configClass);
207+
for (Iterator<ConfigurationClass> it = this.knownSuperclasses.values().iterator(); it.hasNext(); ) {
208+
if (configClass.equals(it.next())) {
209+
it.remove();
210+
}
199211
}
200212
}
201213
}
@@ -207,7 +219,7 @@ protected void processConfigurationClass(ConfigurationClass configClass) throws
207219
}
208220
while (sourceClass != null);
209221

210-
this.configurationClasses.add(configClass);
222+
this.configurationClasses.put(configClass, configClass);
211223
}
212224

213225
/**
@@ -466,13 +478,13 @@ private void invokeAwareMethods(Object importStrategyBean) {
466478
* @see ConfigurationClass#validate
467479
*/
468480
public void validate() {
469-
for (ConfigurationClass configClass : this.configurationClasses) {
481+
for (ConfigurationClass configClass : this.configurationClasses.keySet()) {
470482
configClass.validate(this.problemReporter);
471483
}
472484
}
473485

474486
public Set<ConfigurationClass> getConfigurationClasses() {
475-
return this.configurationClasses;
487+
return this.configurationClasses.keySet();
476488
}
477489

478490
public List<PropertySource<?>> getPropertySources() {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/*
2+
* Copyright 2012-2014 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.configuration;
18+
19+
import org.junit.Test;
20+
21+
import org.springframework.beans.factory.annotation.Autowired;
22+
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
23+
import org.springframework.context.annotation.Bean;
24+
import org.springframework.context.annotation.ConditionContext;
25+
import org.springframework.context.annotation.Conditional;
26+
import org.springframework.context.annotation.Configuration;
27+
import org.springframework.context.annotation.ConfigurationCondition;
28+
import org.springframework.context.annotation.Import;
29+
import org.springframework.core.type.AnnotatedTypeMetadata;
30+
31+
import static org.junit.Assert.*;
32+
33+
/**
34+
* @author Andy Wilkinson
35+
*/
36+
public class ImportWithConditionTests {
37+
38+
private AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
39+
40+
@Test
41+
public void conditionalThenUnconditional() throws Exception {
42+
this.context.register(ConditionalThenUnconditional.class);
43+
this.context.refresh();
44+
assertFalse(this.context.containsBean("beanTwo"));
45+
assertTrue(this.context.containsBean("beanOne"));
46+
}
47+
48+
@Test
49+
public void unconditionalThenConditional() throws Exception {
50+
this.context.register(UnconditionalThenConditional.class);
51+
this.context.refresh();
52+
assertFalse(this.context.containsBean("beanTwo"));
53+
assertTrue(this.context.containsBean("beanOne"));
54+
}
55+
56+
57+
@Configuration
58+
@Import({ConditionalConfiguration.class, UnconditionalConfiguration.class})
59+
protected static class ConditionalThenUnconditional {
60+
61+
@Autowired
62+
private BeanOne beanOne;
63+
}
64+
65+
66+
@Configuration
67+
@Import({UnconditionalConfiguration.class, ConditionalConfiguration.class})
68+
protected static class UnconditionalThenConditional {
69+
70+
@Autowired
71+
private BeanOne beanOne;
72+
}
73+
74+
75+
@Configuration
76+
@Import(BeanProvidingConfiguration.class)
77+
protected static class UnconditionalConfiguration {
78+
}
79+
80+
81+
@Configuration
82+
@Conditional(NeverMatchingCondition.class)
83+
@Import(BeanProvidingConfiguration.class)
84+
protected static class ConditionalConfiguration {
85+
}
86+
87+
88+
@Configuration
89+
protected static class BeanProvidingConfiguration {
90+
91+
@Bean
92+
BeanOne beanOne() {
93+
return new BeanOne();
94+
}
95+
}
96+
97+
98+
private static final class BeanOne {
99+
}
100+
101+
102+
private static final class NeverMatchingCondition implements ConfigurationCondition {
103+
104+
@Override
105+
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
106+
return false;
107+
}
108+
109+
@Override
110+
public ConfigurationPhase getConfigurationPhase() {
111+
return ConfigurationPhase.REGISTER_BEAN;
112+
}
113+
}
114+
115+
}

0 commit comments

Comments
 (0)