From 16ac495084d01beb28af9e535b8b2ca5f31d1d42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Deleuze?= Date: Thu, 30 Nov 2023 15:24:06 +0100 Subject: [PATCH] Introduce ORDER_ATTRIBUTE on AbstractBeanDefinition This commit allows to define a bean order programmatically at bean definition level (functional equivalent of `@Order`). If specified, the order attribute defined at bean definition level overrides potential values set with `@Order`. See gh-30849 --- .../support/AbstractBeanDefinition.java | 13 +++ .../support/DefaultListableBeanFactory.java | 18 ++++- .../DefaultListableBeanFactoryTests.java | 81 +++++++++++++++++++ 3 files changed, 110 insertions(+), 2 deletions(-) diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinition.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinition.java index d8b4f3a41a87..5a65001bdff4 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinition.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/AbstractBeanDefinition.java @@ -51,6 +51,7 @@ * @author Juergen Hoeller * @author Rob Harrop * @author Mark Fisher + * @author Sebastien Deleuze * @see GenericBeanDefinition * @see RootBeanDefinition * @see ChildBeanDefinition @@ -139,6 +140,18 @@ public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccess */ public static final String PREFERRED_CONSTRUCTORS_ATTRIBUTE = "preferredConstructors"; + /** + * The name of an attribute that can be + * {@link org.springframework.core.AttributeAccessor#setAttribute set} on a + * {@link org.springframework.beans.factory.config.BeanDefinition} so that + * bean definitions can indicate the sort order for the targeted bean. + * This is analogous to the {@code @Order} annotation. + * @since 6.1.2 + * @see org.springframework.core.annotation.Order + * @see org.springframework.core.Ordered + */ + public static final String ORDER_ATTRIBUTE = "order"; + /** * Constant that indicates the container should attempt to infer the * {@link #setDestroyMethodName destroy method name} for a bean as opposed to diff --git a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java index 20d406a54727..65ef5680a482 100644 --- a/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java +++ b/spring-beans/src/main/java/org/springframework/beans/factory/support/DefaultListableBeanFactory.java @@ -70,6 +70,7 @@ import org.springframework.beans.factory.config.DependencyDescriptor; import org.springframework.beans.factory.config.NamedBeanHolder; import org.springframework.core.OrderComparator; +import org.springframework.core.Ordered; import org.springframework.core.ResolvableType; import org.springframework.core.annotation.MergedAnnotation; import org.springframework.core.annotation.MergedAnnotations; @@ -110,6 +111,7 @@ * @author Chris Beams * @author Phillip Webb * @author Stephane Nicoll + * @author Sebastien Deleuze * @since 16 April 2001 * @see #registerBeanDefinition * @see #addBeanPostProcessor @@ -2230,7 +2232,9 @@ public Object get() throws BeansException { * that is aware of the bean metadata of the instances to sort. *

Lookup for the method factory of an instance to sort, if any, and let the * comparator retrieve the {@link org.springframework.core.annotation.Order} - * value defined on it. This essentially allows for the following construct: + * value defined on it. + *

As of 6.1.2, this class takes the {@link AbstractBeanDefinition#ORDER_ATTRIBUTE} + * attribute into account. */ private class FactoryAwareOrderSourceProvider implements OrderComparator.OrderSourceProvider { @@ -2249,7 +2253,17 @@ public Object getOrderSource(Object obj) { } try { RootBeanDefinition beanDefinition = (RootBeanDefinition) getMergedBeanDefinition(beanName); - List sources = new ArrayList<>(2); + List sources = new ArrayList<>(3); + Object orderAttribute = beanDefinition.getAttribute(AbstractBeanDefinition.ORDER_ATTRIBUTE); + if (orderAttribute != null) { + if (orderAttribute instanceof Integer order) { + sources.add((Ordered) () -> order); + } + else { + throw new IllegalStateException("Invalid value type for attribute '" + + AbstractBeanDefinition.ORDER_ATTRIBUTE + "': " + orderAttribute.getClass().getName()); + } + } Method factoryMethod = beanDefinition.getResolvedFactoryMethod(); if (factoryMethod != null) { sources.add(factoryMethod); diff --git a/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java b/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java index 8455b9254f60..b9d4ac052b2c 100644 --- a/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java +++ b/spring-beans/src/test/java/org/springframework/beans/factory/DefaultListableBeanFactoryTests.java @@ -79,9 +79,11 @@ import org.springframework.beans.testfixture.beans.factory.DummyFactory; import org.springframework.core.DefaultParameterNameDiscoverer; import org.springframework.core.MethodParameter; +import org.springframework.core.Ordered; import org.springframework.core.ParameterNameDiscoverer; import org.springframework.core.ResolvableType; import org.springframework.core.annotation.AnnotationAwareOrderComparator; +import org.springframework.core.annotation.Order; import org.springframework.core.convert.support.DefaultConversionService; import org.springframework.core.convert.support.GenericConversionService; import org.springframework.core.io.Resource; @@ -1484,6 +1486,55 @@ void autowirePreferredConstructorFromAttribute() throws Exception { assertThat(bean.getSpouse2()).isNull(); } + @Test + void orderFromAttribute() { + GenericBeanDefinition bd1 = new GenericBeanDefinition(); + bd1.setBeanClass(TestBean.class); + bd1.setPropertyValues(new MutablePropertyValues(List.of(new PropertyValue("name", "lowest")))); + bd1.setAttribute(AbstractBeanDefinition.ORDER_ATTRIBUTE, Ordered.LOWEST_PRECEDENCE); + lbf.registerBeanDefinition("bean1", bd1); + GenericBeanDefinition bd2 = new GenericBeanDefinition(); + bd2.setBeanClass(TestBean.class); + bd2.setPropertyValues(new MutablePropertyValues(List.of(new PropertyValue("name", "highest")))); + bd2.setAttribute(AbstractBeanDefinition.ORDER_ATTRIBUTE, Ordered.HIGHEST_PRECEDENCE); + lbf.registerBeanDefinition("bean2", bd2); + assertThat(lbf.getBeanProvider(TestBean.class).orderedStream().map(TestBean::getName)) + .containsExactly("highest", "lowest"); + } + + @Test + void orderFromAttributeOverrideAnnotation() { + lbf.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE); + RootBeanDefinition rbd1 = new RootBeanDefinition(LowestPrecedenceTestBeanFactoryBean.class); + rbd1.setAttribute(AbstractBeanDefinition.ORDER_ATTRIBUTE, Ordered.HIGHEST_PRECEDENCE); + lbf.registerBeanDefinition("lowestPrecedenceFactory", rbd1); + RootBeanDefinition rbd2 = new RootBeanDefinition(HighestPrecedenceTestBeanFactoryBean.class); + rbd2.setAttribute(AbstractBeanDefinition.ORDER_ATTRIBUTE, Ordered.LOWEST_PRECEDENCE); + lbf.registerBeanDefinition("highestPrecedenceFactory", rbd2); + GenericBeanDefinition bd1 = new GenericBeanDefinition(); + bd1.setFactoryBeanName("highestPrecedenceFactory"); + lbf.registerBeanDefinition("bean1", bd1); + GenericBeanDefinition bd2 = new GenericBeanDefinition(); + bd2.setFactoryBeanName("lowestPrecedenceFactory"); + lbf.registerBeanDefinition("bean2", bd2); + assertThat(lbf.getBeanProvider(TestBean.class).orderedStream().map(TestBean::getName)) + .containsExactly("fromLowestPrecedenceTestBeanFactoryBean", "fromHighestPrecedenceTestBeanFactoryBean"); + } + + @Test + void invalidOrderAttribute() { + GenericBeanDefinition bd1 = new GenericBeanDefinition(); + bd1.setBeanClass(TestBean.class); + bd1.setAttribute(AbstractBeanDefinition.ORDER_ATTRIBUTE, Boolean.TRUE); + lbf.registerBeanDefinition("bean1", bd1); + GenericBeanDefinition bd2 = new GenericBeanDefinition(); + bd2.setBeanClass(TestBean.class); + lbf.registerBeanDefinition("bean", bd2); + assertThatIllegalStateException() + .isThrownBy(() -> lbf.getBeanProvider(TestBean.class).orderedStream().collect(Collectors.toList())) + .withMessageContaining("Invalid value type for attribute"); + } + @Test void dependsOnCycle() { RootBeanDefinition bd1 = new RootBeanDefinition(TestBean.class); @@ -3426,4 +3477,34 @@ public NonPublicEnum getNonPublicEnum() { } } + @Order + private static class LowestPrecedenceTestBeanFactoryBean implements FactoryBean { + + @Override + public TestBean getObject() { + return new TestBean("fromLowestPrecedenceTestBeanFactoryBean"); + } + + @Override + public Class getObjectType() { + return TestBean.class; + } + + } + + @Order(Ordered.HIGHEST_PRECEDENCE) + private static class HighestPrecedenceTestBeanFactoryBean implements FactoryBean { + + @Override + public TestBean getObject() { + return new TestBean("fromHighestPrecedenceTestBeanFactoryBean"); + } + + @Override + public Class getObjectType() { + return TestBean.class; + } + + } + }