@Configuration classes get processed according to their @Order (if applicable)

Issue: SPR-12657
This commit is contained in:
Juergen Hoeller 2015-03-13 18:18:21 +01:00
parent 9d497cbd98
commit f5b4e18209
3 changed files with 97 additions and 8 deletions

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2014 the original author or authors. * Copyright 2002-2015 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -17,10 +17,14 @@
package org.springframework.context.annotation; package org.springframework.context.annotation;
import java.beans.PropertyDescriptor; import java.beans.PropertyDescriptor;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -264,7 +268,7 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
* {@link Configuration} classes. * {@link Configuration} classes.
*/ */
public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) { public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
Set<BeanDefinitionHolder> configCandidates = new LinkedHashSet<BeanDefinitionHolder>(); List<BeanDefinitionHolder> configCandidates = new ArrayList<BeanDefinitionHolder>();
String[] candidateNames = registry.getBeanDefinitionNames(); String[] candidateNames = registry.getBeanDefinitionNames();
for (String beanName : candidateNames) { for (String beanName : candidateNames) {
@ -285,6 +289,16 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
return; return;
} }
// Sort by previously determined @Order value, if applicable
Collections.sort(configCandidates, new Comparator<BeanDefinitionHolder>() {
@Override
public int compare(BeanDefinitionHolder bd1, BeanDefinitionHolder bd2) {
int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
return (i1 < i2) ? -1 : (i1 > i2) ? 1 : 0;
}
});
// Detect any custom bean name generation strategy supplied through the enclosing application context // Detect any custom bean name generation strategy supplied through the enclosing application context
SingletonBeanRegistry singletonRegistry = null; SingletonBeanRegistry singletonRegistry = null;
if (registry instanceof SingletonBeanRegistry) { if (registry instanceof SingletonBeanRegistry) {
@ -301,9 +315,10 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
this.metadataReaderFactory, this.problemReporter, this.environment, this.metadataReaderFactory, this.problemReporter, this.environment,
this.resourceLoader, this.componentScanBeanNameGenerator, registry); this.resourceLoader, this.componentScanBeanNameGenerator, registry);
Set<BeanDefinitionHolder> candidates = new LinkedHashSet<BeanDefinitionHolder>(configCandidates);
Set<ConfigurationClass> alreadyParsed = new HashSet<ConfigurationClass>(configCandidates.size()); Set<ConfigurationClass> alreadyParsed = new HashSet<ConfigurationClass>(configCandidates.size());
do { do {
parser.parse(configCandidates); parser.parse(candidates);
parser.validate(); parser.validate();
Set<ConfigurationClass> configClasses = new LinkedHashSet<ConfigurationClass>(parser.getConfigurationClasses()); Set<ConfigurationClass> configClasses = new LinkedHashSet<ConfigurationClass>(parser.getConfigurationClasses());
@ -318,7 +333,7 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
this.reader.loadBeanDefinitions(configClasses); this.reader.loadBeanDefinitions(configClasses);
alreadyParsed.addAll(configClasses); alreadyParsed.addAll(configClasses);
configCandidates.clear(); candidates.clear();
if (registry.getBeanDefinitionCount() > candidateNames.length) { if (registry.getBeanDefinitionCount() > candidateNames.length) {
String[] newCandidateNames = registry.getBeanDefinitionNames(); String[] newCandidateNames = registry.getBeanDefinitionNames();
Set<String> oldCandidateNames = new HashSet<String>(Arrays.asList(candidateNames)); Set<String> oldCandidateNames = new HashSet<String>(Arrays.asList(candidateNames));
@ -331,14 +346,14 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo
BeanDefinition beanDef = registry.getBeanDefinition(candidateName); BeanDefinition beanDef = registry.getBeanDefinition(candidateName);
if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory) && if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory) &&
!alreadyParsedClasses.contains(beanDef.getBeanClassName())) { !alreadyParsedClasses.contains(beanDef.getBeanClassName())) {
configCandidates.add(new BeanDefinitionHolder(beanDef, candidateName)); candidates.add(new BeanDefinitionHolder(beanDef, candidateName));
} }
} }
} }
candidateNames = newCandidateNames; candidateNames = newCandidateNames;
} }
} }
while (!configCandidates.isEmpty()); while (!candidates.isEmpty());
// Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes // Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes
if (singletonRegistry != null) { if (singletonRegistry != null) {

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2014 the original author or authors. * Copyright 2002-2015 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -18,6 +18,7 @@ package org.springframework.context.annotation;
import java.io.IOException; import java.io.IOException;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map;
import java.util.Set; import java.util.Set;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
@ -27,6 +28,9 @@ import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.core.Conventions; import org.springframework.core.Conventions;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.core.type.AnnotationMetadata; import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.StandardAnnotationMetadata; import org.springframework.core.type.StandardAnnotationMetadata;
import org.springframework.core.type.classreading.MetadataReader; import org.springframework.core.type.classreading.MetadataReader;
@ -49,6 +53,9 @@ abstract class ConfigurationClassUtils {
private static final String CONFIGURATION_CLASS_ATTRIBUTE = private static final String CONFIGURATION_CLASS_ATTRIBUTE =
Conventions.getQualifiedAttributeName(ConfigurationClassPostProcessor.class, "configurationClass"); Conventions.getQualifiedAttributeName(ConfigurationClassPostProcessor.class, "configurationClass");
private static final String ORDER_ATTRIBUTE =
Conventions.getQualifiedAttributeName(ConfigurationClassPostProcessor.class, "order");
private static final Log logger = LogFactory.getLog(ConfigurationClassUtils.class); private static final Log logger = LogFactory.getLog(ConfigurationClassUtils.class);
@ -101,6 +108,11 @@ abstract class ConfigurationClassUtils {
} }
} }
Map<String, Object> orderAttributes = metadata.getAnnotationAttributes(Order.class.getName());
if (orderAttributes != null) {
beanDef.setAttribute(ORDER_ATTRIBUTE, orderAttributes.get(AnnotationUtils.VALUE));
}
if (isFullConfigurationCandidate(metadata)) { if (isFullConfigurationCandidate(metadata)) {
beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL); beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);
return true; return true;
@ -173,4 +185,16 @@ abstract class ConfigurationClassUtils {
return CONFIGURATION_CLASS_LITE.equals(beanDef.getAttribute(CONFIGURATION_CLASS_ATTRIBUTE)); return CONFIGURATION_CLASS_LITE.equals(beanDef.getAttribute(CONFIGURATION_CLASS_ATTRIBUTE));
} }
/**
* Determine the order for the given configuration class bean definition,
* as set by {@link #checkConfigurationClassCandidate}.
* @param beanDef the bean definition to check
* @return the {@link @Order} annotation value on the configuration class,
* or {@link Ordered#LOWEST_PRECEDENCE} if none declared
*/
public static int getOrder(BeanDefinition beanDef) {
Integer order = (Integer) beanDef.getAttribute(ORDER_ATTRIBUTE);
return (order != null ? order : Ordered.LOWEST_PRECEDENCE);
}
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2014 the original author or authors. * Copyright 2002-2015 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -28,6 +28,7 @@ import org.junit.Test;
import org.springframework.aop.scope.ScopedObject; import org.springframework.aop.scope.ScopedObject;
import org.springframework.aop.scope.ScopedProxyUtils; import org.springframework.aop.scope.ScopedProxyUtils;
import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.NoSuchBeanDefinitionException; import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -40,6 +41,7 @@ import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.componentscan.simple.SimpleComponent; import org.springframework.context.annotation.componentscan.simple.SimpleComponent;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.StandardEnvironment; import org.springframework.core.env.StandardEnvironment;
import org.springframework.core.io.DescriptiveResource; import org.springframework.core.io.DescriptiveResource;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@ -271,6 +273,35 @@ public class ConfigurationClassPostProcessorTests {
beanFactory.getBean("bar", TestBean.class); beanFactory.getBean("bar", TestBean.class);
} }
@Test
public void postProcessorFailsOnImplicitOverrideIfOverridingIsNotAllowed() {
RootBeanDefinition rbd = new RootBeanDefinition(TestBean.class);
rbd.setResource(new DescriptiveResource("XML or something"));
beanFactory.registerBeanDefinition("bar", rbd);
beanFactory.registerBeanDefinition("config", new RootBeanDefinition(SingletonBeanConfig.class));
beanFactory.setAllowBeanDefinitionOverriding(false);
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
try {
pp.postProcessBeanFactory(beanFactory);
fail("Should have thrown BeanDefinitionStoreException");
}
catch (BeanDefinitionStoreException ex) {
assertTrue(ex.getMessage().contains("bar"));
assertTrue(ex.getMessage().contains("SingletonBeanConfig"));
assertTrue(ex.getMessage().contains(TestBean.class.getName()));
}
}
@Test
public void configurationClassesProcessedInCorrectOrder() {
beanFactory.registerBeanDefinition("config1", new RootBeanDefinition(OverridingSingletonBeanConfig.class));
beanFactory.registerBeanDefinition("config2", new RootBeanDefinition(SingletonBeanConfig.class));
ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor();
pp.postProcessBeanFactory(beanFactory);
assertTrue(beanFactory.getBean(Foo.class) instanceof ExtendedFoo);
beanFactory.getBean(Bar.class);
}
@Test @Test
public void scopedProxyTargetMarkedAsNonAutowireCandidate() { public void scopedProxyTargetMarkedAsNonAutowireCandidate() {
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
@ -485,6 +516,7 @@ public class ConfigurationClassPostProcessorTests {
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
@Configuration @Configuration
@Order(1)
static class SingletonBeanConfig { static class SingletonBeanConfig {
public @Bean public @Bean
@ -498,9 +530,27 @@ public class ConfigurationClassPostProcessorTests {
} }
} }
@Configuration
@Order(2)
static class OverridingSingletonBeanConfig {
public @Bean
ExtendedFoo foo() {
return new ExtendedFoo();
}
public @Bean
Bar bar() {
return new Bar(foo());
}
}
static class Foo { static class Foo {
} }
static class ExtendedFoo extends Foo {
}
static class Bar { static class Bar {
final Foo foo; final Foo foo;