Skip to content

Commit

Permalink
Introduce ORDER_ATTRIBUTE on AbstractBeanDefinition
Browse files Browse the repository at this point in the history
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 spring-projectsgh-30849
  • Loading branch information
sdeleuze committed Nov 30, 2023
1 parent a506238 commit 16ac495
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
* @author Juergen Hoeller
* @author Rob Harrop
* @author Mark Fisher
* @author Sebastien Deleuze
* @see GenericBeanDefinition
* @see RootBeanDefinition
* @see ChildBeanDefinition
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -110,6 +111,7 @@
* @author Chris Beams
* @author Phillip Webb
* @author Stephane Nicoll
* @author Sebastien Deleuze
* @since 16 April 2001
* @see #registerBeanDefinition
* @see #addBeanPostProcessor
Expand Down Expand Up @@ -2230,7 +2232,9 @@ public Object get() throws BeansException {
* that is aware of the bean metadata of the instances to sort.
* <p>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.
* <p>As of 6.1.2, this class takes the {@link AbstractBeanDefinition#ORDER_ATTRIBUTE}
* attribute into account.
*/
private class FactoryAwareOrderSourceProvider implements OrderComparator.OrderSourceProvider {

Expand All @@ -2249,7 +2253,17 @@ public Object getOrderSource(Object obj) {
}
try {
RootBeanDefinition beanDefinition = (RootBeanDefinition) getMergedBeanDefinition(beanName);
List<Object> sources = new ArrayList<>(2);
List<Object> 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -3426,4 +3477,34 @@ public NonPublicEnum getNonPublicEnum() {
}
}

@Order
private static class LowestPrecedenceTestBeanFactoryBean implements FactoryBean<TestBean> {

@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<TestBean> {

@Override
public TestBean getObject() {
return new TestBean("fromHighestPrecedenceTestBeanFactoryBean");
}

@Override
public Class<?> getObjectType() {
return TestBean.class;
}

}

}

0 comments on commit 16ac495

Please sign in to comment.