Skip to content

Commit f5b4e18

Browse files
committed
@configuration classes get processed according to their @order (if applicable)
Issue: SPR-12657
1 parent 9d497cb commit f5b4e18

File tree

3 files changed

+97
-8
lines changed

3 files changed

+97
-8
lines changed

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

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2014 the original author or authors.
2+
* Copyright 2002-2015 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.
@@ -17,10 +17,14 @@
1717
package org.springframework.context.annotation;
1818

1919
import java.beans.PropertyDescriptor;
20+
import java.util.ArrayList;
2021
import java.util.Arrays;
22+
import java.util.Collections;
23+
import java.util.Comparator;
2124
import java.util.HashSet;
2225
import java.util.LinkedHashMap;
2326
import java.util.LinkedHashSet;
27+
import java.util.List;
2428
import java.util.Map;
2529
import java.util.Set;
2630

@@ -264,7 +268,7 @@ public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
264268
* {@link Configuration} classes.
265269
*/
266270
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
267-
Set<BeanDefinitionHolder> configCandidates = new LinkedHashSet<BeanDefinitionHolder>();
271+
List<BeanDefinitionHolder> configCandidates = new ArrayList<BeanDefinitionHolder>();
268272
String[] candidateNames = registry.getBeanDefinitionNames();
269273

270274
for (String beanName : candidateNames) {
@@ -285,6 +289,16 @@ else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.
285289
return;
286290
}
287291

292+
// Sort by previously determined @Order value, if applicable
293+
Collections.sort(configCandidates, new Comparator<BeanDefinitionHolder>() {
294+
@Override
295+
public int compare(BeanDefinitionHolder bd1, BeanDefinitionHolder bd2) {
296+
int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
297+
int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
298+
return (i1 < i2) ? -1 : (i1 > i2) ? 1 : 0;
299+
}
300+
});
301+
288302
// Detect any custom bean name generation strategy supplied through the enclosing application context
289303
SingletonBeanRegistry singletonRegistry = null;
290304
if (registry instanceof SingletonBeanRegistry) {
@@ -301,9 +315,10 @@ else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.
301315
this.metadataReaderFactory, this.problemReporter, this.environment,
302316
this.resourceLoader, this.componentScanBeanNameGenerator, registry);
303317

318+
Set<BeanDefinitionHolder> candidates = new LinkedHashSet<BeanDefinitionHolder>(configCandidates);
304319
Set<ConfigurationClass> alreadyParsed = new HashSet<ConfigurationClass>(configCandidates.size());
305320
do {
306-
parser.parse(configCandidates);
321+
parser.parse(candidates);
307322
parser.validate();
308323

309324
Set<ConfigurationClass> configClasses = new LinkedHashSet<ConfigurationClass>(parser.getConfigurationClasses());
@@ -318,7 +333,7 @@ else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.
318333
this.reader.loadBeanDefinitions(configClasses);
319334
alreadyParsed.addAll(configClasses);
320335

321-
configCandidates.clear();
336+
candidates.clear();
322337
if (registry.getBeanDefinitionCount() > candidateNames.length) {
323338
String[] newCandidateNames = registry.getBeanDefinitionNames();
324339
Set<String> oldCandidateNames = new HashSet<String>(Arrays.asList(candidateNames));
@@ -331,14 +346,14 @@ else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.
331346
BeanDefinition beanDef = registry.getBeanDefinition(candidateName);
332347
if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory) &&
333348
!alreadyParsedClasses.contains(beanDef.getBeanClassName())) {
334-
configCandidates.add(new BeanDefinitionHolder(beanDef, candidateName));
349+
candidates.add(new BeanDefinitionHolder(beanDef, candidateName));
335350
}
336351
}
337352
}
338353
candidateNames = newCandidateNames;
339354
}
340355
}
341-
while (!configCandidates.isEmpty());
356+
while (!candidates.isEmpty());
342357

343358
// Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes
344359
if (singletonRegistry != null) {

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

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2014 the original author or authors.
2+
* Copyright 2002-2015 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.
@@ -18,6 +18,7 @@
1818

1919
import java.io.IOException;
2020
import java.util.HashSet;
21+
import java.util.Map;
2122
import java.util.Set;
2223

2324
import org.apache.commons.logging.Log;
@@ -27,6 +28,9 @@
2728
import org.springframework.beans.factory.config.BeanDefinition;
2829
import org.springframework.beans.factory.support.AbstractBeanDefinition;
2930
import org.springframework.core.Conventions;
31+
import org.springframework.core.Ordered;
32+
import org.springframework.core.annotation.AnnotationUtils;
33+
import org.springframework.core.annotation.Order;
3034
import org.springframework.core.type.AnnotationMetadata;
3135
import org.springframework.core.type.StandardAnnotationMetadata;
3236
import org.springframework.core.type.classreading.MetadataReader;
@@ -49,6 +53,9 @@ abstract class ConfigurationClassUtils {
4953
private static final String CONFIGURATION_CLASS_ATTRIBUTE =
5054
Conventions.getQualifiedAttributeName(ConfigurationClassPostProcessor.class, "configurationClass");
5155

56+
private static final String ORDER_ATTRIBUTE =
57+
Conventions.getQualifiedAttributeName(ConfigurationClassPostProcessor.class, "order");
58+
5259

5360
private static final Log logger = LogFactory.getLog(ConfigurationClassUtils.class);
5461

@@ -101,6 +108,11 @@ else if (beanDef instanceof AbstractBeanDefinition && ((AbstractBeanDefinition)
101108
}
102109
}
103110

111+
Map<String, Object> orderAttributes = metadata.getAnnotationAttributes(Order.class.getName());
112+
if (orderAttributes != null) {
113+
beanDef.setAttribute(ORDER_ATTRIBUTE, orderAttributes.get(AnnotationUtils.VALUE));
114+
}
115+
104116
if (isFullConfigurationCandidate(metadata)) {
105117
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
106118
return true;
@@ -173,4 +185,16 @@ public static boolean isLiteConfigurationClass(BeanDefinition beanDef) {
173185
return CONFIGURATION_CLASS_LITE.equals(beanDef.getAttribute(CONFIGURATION_CLASS_ATTRIBUTE));
174186
}
175187

188+
/**
189+
* Determine the order for the given configuration class bean definition,
190+
* as set by {@link #checkConfigurationClassCandidate}.
191+
* @param beanDef the bean definition to check
192+
* @return the {@link @Order} annotation value on the configuration class,
193+
* or {@link Ordered#LOWEST_PRECEDENCE} if none declared
194+
*/
195+
public static int getOrder(BeanDefinition beanDef) {
196+
Integer order = (Integer) beanDef.getAttribute(ORDER_ATTRIBUTE);
197+
return (order != null ? order : Ordered.LOWEST_PRECEDENCE);
198+
}
199+
176200
}

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

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2014 the original author or authors.
2+
* Copyright 2002-2015 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.
@@ -28,6 +28,7 @@
2828
import org.springframework.aop.scope.ScopedObject;
2929
import org.springframework.aop.scope.ScopedProxyUtils;
3030
import org.springframework.beans.factory.BeanCreationException;
31+
import org.springframework.beans.factory.BeanDefinitionStoreException;
3132
import org.springframework.beans.factory.FactoryBean;
3233
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
3334
import org.springframework.beans.factory.annotation.Autowired;
@@ -40,6 +41,7 @@
4041
import org.springframework.beans.factory.support.RootBeanDefinition;
4142
import org.springframework.context.ApplicationContext;
4243
import org.springframework.context.annotation.componentscan.simple.SimpleComponent;
44+
import org.springframework.core.annotation.Order;
4345
import org.springframework.core.env.StandardEnvironment;
4446
import org.springframework.core.io.DescriptiveResource;
4547
import org.springframework.stereotype.Component;
@@ -271,6 +273,35 @@ public void postProcessorDoesNotOverrideRegularBeanDefinitionsEvenWithScopedProx
271273
beanFactory.getBean("bar", TestBean.class);
272274
}
273275

276+
@Test
277+
public void postProcessorFailsOnImplicitOverrideIfOverridingIsNotAllowed() {
278+
RootBeanDefinition rbd = new RootBeanDefinition(TestBean.class);
279+
rbd.setResource(new DescriptiveResource("XML or something"));
280+
beanFactory.registerBeanDefinition("bar", rbd);
281+
beanFactory.registerBeanDefinition("config", new RootBeanDefinition(SingletonBeanConfig.class));
282+
beanFactory.setAllowBeanDefinitionOverriding(false);
283+
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
284+
try {
285+
pp.postProcessBeanFactory(beanFactory);
286+
fail("Should have thrown BeanDefinitionStoreException");
287+
}
288+
catch (BeanDefinitionStoreException ex) {
289+
assertTrue(ex.getMessage().contains("bar"));
290+
assertTrue(ex.getMessage().contains("SingletonBeanConfig"));
291+
assertTrue(ex.getMessage().contains(TestBean.class.getName()));
292+
}
293+
}
294+
295+
@Test
296+
public void configurationClassesProcessedInCorrectOrder() {
297+
beanFactory.registerBeanDefinition("config1", new RootBeanDefinition(OverridingSingletonBeanConfig.class));
298+
beanFactory.registerBeanDefinition("config2", new RootBeanDefinition(SingletonBeanConfig.class));
299+
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
300+
pp.postProcessBeanFactory(beanFactory);
301+
assertTrue(beanFactory.getBean(Foo.class) instanceof ExtendedFoo);
302+
beanFactory.getBean(Bar.class);
303+
}
304+
274305
@Test
275306
public void scopedProxyTargetMarkedAsNonAutowireCandidate() {
276307
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
@@ -485,6 +516,7 @@ public void testSingletonArgumentsThroughBeanMethodCall() {
485516
// -------------------------------------------------------------------------
486517

487518
@Configuration
519+
@Order(1)
488520
static class SingletonBeanConfig {
489521

490522
public @Bean
@@ -498,9 +530,27 @@ Bar bar() {
498530
}
499531
}
500532

533+
@Configuration
534+
@Order(2)
535+
static class OverridingSingletonBeanConfig {
536+
537+
public @Bean
538+
ExtendedFoo foo() {
539+
return new ExtendedFoo();
540+
}
541+
542+
public @Bean
543+
Bar bar() {
544+
return new Bar(foo());
545+
}
546+
}
547+
501548
static class Foo {
502549
}
503550

551+
static class ExtendedFoo extends Foo {
552+
}
553+
504554
static class Bar {
505555

506556
final Foo foo;

0 commit comments

Comments
 (0)