From 917207b7aebfb67f5026254b52e75fa2043ed29f Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Tue, 4 Apr 2017 17:17:03 +0200 Subject: [PATCH] Support for @Order on nested configuration classes Issue: SPR-15384 --- .../annotation/ConfigurationClassParser.java | 26 +++++++++--- .../annotation/ConfigurationClassUtils.java | 20 +++++++-- .../ConfigurationClassPostProcessorTests.java | 42 +++++++++++++++++++ 3 files changed, 79 insertions(+), 9 deletions(-) diff --git a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java index a7424a1ecb..551e06acc1 100644 --- a/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java +++ b/spring-context/src/main/java/org/springframework/context/annotation/ConfigurationClassParser.java @@ -50,6 +50,8 @@ import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.BeanNameGenerator; import org.springframework.context.annotation.ConfigurationCondition.ConfigurationPhase; import org.springframework.core.NestedIOException; +import org.springframework.core.OrderComparator; +import org.springframework.core.Ordered; import org.springframework.core.annotation.AnnotationAttributes; import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.core.env.CompositePropertySource; @@ -330,16 +332,24 @@ class ConfigurationClassParser { * Register member (nested) classes that happen to be configuration classes themselves. */ private void processMemberClasses(ConfigurationClass configClass, SourceClass sourceClass) throws IOException { - for (SourceClass memberClass : sourceClass.getMemberClasses()) { - if (ConfigurationClassUtils.isConfigurationCandidate(memberClass.getMetadata()) && - !memberClass.getMetadata().getClassName().equals(configClass.getMetadata().getClassName())) { + Collection memberClasses = sourceClass.getMemberClasses(); + if (!memberClasses.isEmpty()) { + List candidates = new ArrayList<>(memberClasses.size()); + for (SourceClass memberClass : memberClasses) { + if (ConfigurationClassUtils.isConfigurationCandidate(memberClass.getMetadata()) && + !memberClass.getMetadata().getClassName().equals(configClass.getMetadata().getClassName())) { + candidates.add(memberClass); + } + } + OrderComparator.sort(candidates); + for (SourceClass candidate : candidates) { if (this.importStack.contains(configClass)) { this.problemReporter.error(new CircularImportProblem(configClass, this.importStack)); } else { this.importStack.push(configClass); try { - processConfigurationClass(memberClass.asConfigClass(configClass)); + processConfigurationClass(candidate.asConfigClass(configClass)); } finally { this.importStack.pop(); @@ -747,7 +757,7 @@ class ConfigurationClassParser { * Simple wrapper that allows annotated source classes to be dealt with * in a uniform manner, regardless of how they are loaded. */ - private class SourceClass { + private class SourceClass implements Ordered { private final Object source; // Class or MetadataReader @@ -767,6 +777,12 @@ class ConfigurationClassParser { return this.metadata; } + @Override + public int getOrder() { + Integer order = ConfigurationClassUtils.getOrder(this.metadata); + return (order != null ? order : Ordered.LOWEST_PRECEDENCE); + } + public Class loadClass() throws ClassNotFoundException { if (this.source instanceof Class) { return (Class) this.source; 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 223f2977be..f5117f54e8 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-2016 the original author or authors. + * Copyright 2002-2017 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. @@ -119,9 +119,9 @@ abstract class ConfigurationClassUtils { } // It's a full or lite configuration candidate... Let's determine the order value, if any. - Map orderAttributes = metadata.getAnnotationAttributes(Order.class.getName()); - if (orderAttributes != null) { - beanDef.setAttribute(ORDER_ATTRIBUTE, orderAttributes.get(AnnotationUtils.VALUE)); + Integer order = getOrder(metadata); + if (order != null) { + beanDef.setAttribute(ORDER_ATTRIBUTE, order); } return true; @@ -198,6 +198,18 @@ abstract class ConfigurationClassUtils { return CONFIGURATION_CLASS_LITE.equals(beanDef.getAttribute(CONFIGURATION_CLASS_ATTRIBUTE)); } + /** + * Determine the order for the given configuration class metadata. + * @param metadata the metadata of the annotated class + * @return the {@link @Order} annotation value on the configuration class, + * or {@link Ordered#LOWEST_PRECEDENCE} if none declared + * @since 5.0 + */ + public static Integer getOrder(AnnotationMetadata metadata) { + Map orderAttributes = metadata.getAnnotationAttributes(Order.class.getName()); + return (orderAttributes != null ? ((Integer) orderAttributes.get(AnnotationUtils.VALUE)) : null); + } + /** * Determine the order for the given configuration class bean definition, * as set by {@link #checkConfigurationClassCandidate}. 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 b5d75c8fb0..0b6ed8a7a1 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 @@ -348,6 +348,18 @@ public class ConfigurationClassPostProcessorTests { } } + @Test + public void nestedConfigurationClassesProcessedInCorrectOrder() { + beanFactory.registerBeanDefinition("config", new RootBeanDefinition(ConfigWithOrderedNestedClasses.class)); + ConfigurationClassPostProcessor pp = new ConfigurationClassPostProcessor(); + pp.postProcessBeanFactory(beanFactory); + + Foo foo = beanFactory.getBean(Foo.class); + assertTrue(foo instanceof ExtendedFoo); + Bar bar = beanFactory.getBean(Bar.class); + assertSame(foo, bar.foo); + } + @Test public void scopedProxyTargetMarkedAsNonAutowireCandidate() { AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor(); @@ -833,6 +845,36 @@ public class ConfigurationClassPostProcessorTests { } } + @Configuration + static class ConfigWithOrderedNestedClasses { + + @Configuration + @Order(1) + static class SingletonBeanConfig { + + public @Bean Foo foo() { + return new Foo(); + } + + public @Bean Bar bar() { + return new Bar(foo()); + } + } + + @Configuration + @Order(2) + static class OverridingSingletonBeanConfig { + + public @Bean ExtendedFoo foo() { + return new ExtendedFoo(); + } + + public @Bean Bar bar() { + return new Bar(foo()); + } + } + } + static class Foo { }