From f5b4e18209c45c948ab6bce6d166ef17b3f5b25d Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Fri, 13 Mar 2015 18:18:21 +0100 Subject: [PATCH] @Configuration classes get processed according to their @Order (if applicable) Issue: SPR-12657 --- .../ConfigurationClassPostProcessor.java | 27 +++++++--- .../annotation/ConfigurationClassUtils.java | 26 +++++++++- .../ConfigurationClassPostProcessorTests.java | 52 ++++++++++++++++++- 3 files changed, 97 insertions(+), 8 deletions(-) diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java index a25d6c30915..12aa9897323 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassPostProcessor.java @@ -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"); * you may not use this file except in compliance with the License. @@ -17,10 +17,14 @@ package org.springframework.context.annotation; import java.beans.PropertyDescriptor; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; +import java.util.List; import java.util.Map; import java.util.Set; @@ -264,7 +268,7 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo * {@link Configuration} classes. */ public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) { - Set configCandidates = new LinkedHashSet(); + List configCandidates = new ArrayList(); String[] candidateNames = registry.getBeanDefinitionNames(); for (String beanName : candidateNames) { @@ -285,6 +289,16 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo return; } + // Sort by previously determined @Order value, if applicable + Collections.sort(configCandidates, new Comparator() { + @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 SingletonBeanRegistry singletonRegistry = null; if (registry instanceof SingletonBeanRegistry) { @@ -301,9 +315,10 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo this.metadataReaderFactory, this.problemReporter, this.environment, this.resourceLoader, this.componentScanBeanNameGenerator, registry); + Set candidates = new LinkedHashSet(configCandidates); Set alreadyParsed = new HashSet(configCandidates.size()); do { - parser.parse(configCandidates); + parser.parse(candidates); parser.validate(); Set configClasses = new LinkedHashSet(parser.getConfigurationClasses()); @@ -318,7 +333,7 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo this.reader.loadBeanDefinitions(configClasses); alreadyParsed.addAll(configClasses); - configCandidates.clear(); + candidates.clear(); if (registry.getBeanDefinitionCount() > candidateNames.length) { String[] newCandidateNames = registry.getBeanDefinitionNames(); Set oldCandidateNames = new HashSet(Arrays.asList(candidateNames)); @@ -331,14 +346,14 @@ public class ConfigurationClassPostProcessor implements BeanDefinitionRegistryPo BeanDefinition beanDef = registry.getBeanDefinition(candidateName); if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory) && !alreadyParsedClasses.contains(beanDef.getBeanClassName())) { - configCandidates.add(new BeanDefinitionHolder(beanDef, candidateName)); + candidates.add(new BeanDefinitionHolder(beanDef, candidateName)); } } } candidateNames = newCandidateNames; } } - while (!configCandidates.isEmpty()); + while (!candidates.isEmpty()); // Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes if (singletonRegistry != null) { diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassUtils.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassUtils.java index c462fa1fc0e..38d2103eac9 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassUtils.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassUtils.java @@ -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"); * 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.util.HashSet; +import java.util.Map; import java.util.Set; 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.support.AbstractBeanDefinition; 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.StandardAnnotationMetadata; import org.springframework.core.type.classreading.MetadataReader; @@ -49,6 +53,9 @@ abstract class ConfigurationClassUtils { private static final String CONFIGURATION_CLASS_ATTRIBUTE = 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); @@ -101,6 +108,11 @@ abstract class ConfigurationClassUtils { } } + Map orderAttributes = metadata.getAnnotationAttributes(Order.class.getName()); + if (orderAttributes != null) { + beanDef.setAttribute(ORDER_ATTRIBUTE, orderAttributes.get(AnnotationUtils.VALUE)); + } + if (isFullConfigurationCandidate(metadata)) { beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL); return true; @@ -173,4 +185,16 @@ abstract class ConfigurationClassUtils { 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); + } + } diff --git a/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorTests.java b/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorTests.java index 4b6d08df967..a3a6a8dcfea 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostProcessorTests.java @@ -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"); * 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.ScopedProxyUtils; import org.springframework.beans.factory.BeanCreationException; +import org.springframework.beans.factory.BeanDefinitionStoreException; import org.springframework.beans.factory.FactoryBean; import org.springframework.beans.factory.NoSuchBeanDefinitionException; 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.context.ApplicationContext; import org.springframework.context.annotation.componentscan.simple.SimpleComponent; +import org.springframework.core.annotation.Order; import org.springframework.core.env.StandardEnvironment; import org.springframework.core.io.DescriptiveResource; import org.springframework.stereotype.Component; @@ -271,6 +273,35 @@ public class ConfigurationClassPostProcessorTests { 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 public void scopedProxyTargetMarkedAsNonAutowireCandidate() { AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); @@ -485,6 +516,7 @@ public class ConfigurationClassPostProcessorTests { // ------------------------------------------------------------------------- @Configuration + @Order(1) static class SingletonBeanConfig { 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 ExtendedFoo extends Foo { + } + static class Bar { final Foo foo;