From 1f4199ea64b5a195ab519b922db283a5fb51a1f7 Mon Sep 17 00:00:00 2001 From: Yanming Zhou Date: Wed, 17 Jun 2026 16:05:08 +0800 Subject: [PATCH] Introduce TailOrdered as opposite of PriorityOrdered Close GH-34670 Signed-off-by: Yanming Zhou --- .../DefaultListableBeanFactoryTests.java | 56 +++++++++++++++ .../springframework/core/OrderComparator.java | 10 +++ .../springframework/core/PriorityOrdered.java | 1 + .../org/springframework/core/TailOrdered.java | 35 ++++++++++ .../core/OrderComparatorTests.java | 70 +++++++++++++++++++ 5 files changed, 172 insertions(+) create mode 100644 spring-core/src/main/java/org/springframework/core/TailOrdered.java 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 c9831eeaec19..2a8b4cb66516 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 @@ -81,6 +81,7 @@ import org.springframework.core.Ordered; import org.springframework.core.ParameterizedTypeReference; import org.springframework.core.ResolvableType; +import org.springframework.core.TailOrdered; import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.core.annotation.Order; import org.springframework.core.convert.support.DefaultConversionService; @@ -1531,6 +1532,43 @@ void autowirePreferredConstructorFromAttribute() throws Exception { assertThat(bean.getSpouse2()).isNull(); } + @Test + void ordering() { + 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(DerivedTestBean.class); + bd2.setPropertyValues(new MutablePropertyValues(List.of(new PropertyValue("name", "highest")))); + bd2.setAttribute(AbstractBeanDefinition.ORDER_ATTRIBUTE, Ordered.HIGHEST_PRECEDENCE); + bd2.setScope(BeanDefinition.SCOPE_PROTOTYPE); + lbf.registerBeanDefinition("bean2", bd2); + GenericBeanDefinition bd3 = new GenericBeanDefinition(); + bd3.setBeanClass(HighestTailPrecedenceTestBean.class); + bd3.setPropertyValues(new MutablePropertyValues(List.of(new PropertyValue("name", "highestTail")))); + lbf.registerBeanDefinition("bean3", bd3); + GenericBeanDefinition bd4 = new GenericBeanDefinition(); + bd4.setBeanClass(LowestTailPrecedenceTestBean.class); + bd4.setPropertyValues(new MutablePropertyValues(List.of(new PropertyValue("name", "lowestTail")))); + lbf.registerBeanDefinition("bean4", bd4); + + assertThat(lbf.getBeanProvider(TestBean.class).orderedStream().map(TestBean::getName)) + .containsExactly("highest", "lowest", "highestTail", "lowestTail"); + assertThat(lbf.getBeanProvider(TestBean.class).orderedStream(clazz -> !DerivedTestBean.class.isAssignableFrom(clazz)) + .map(TestBean::getName)).containsExactly("lowest", "highestTail", "lowestTail"); + assertThat(lbf.getBeanProvider(TestBean.class).orderedStream(ObjectProvider.UNFILTERED).map(TestBean::getName)) + .containsExactly("highest", "lowest", "highestTail", "lowestTail"); + assertThat(lbf.getBeanProvider(TestBean.class).orderedStream(ObjectProvider.UNFILTERED, false).map(TestBean::getName)) + .containsExactly("lowest", "highestTail", "lowestTail"); + + assertThat(lbf.getOrder("bean1")).isEqualTo(Ordered.LOWEST_PRECEDENCE); + assertThat(lbf.getOrder("bean2")).isEqualTo(Ordered.HIGHEST_PRECEDENCE); + assertThat(lbf.getOrder("bean3")).isEqualTo(Ordered.HIGHEST_PRECEDENCE); + assertThat(lbf.getOrder("bean4")).isEqualTo(Ordered.LOWEST_PRECEDENCE); + } + @Test void orderFromAttribute() { GenericBeanDefinition bd1 = new GenericBeanDefinition(); @@ -3896,6 +3934,24 @@ public Class getObjectType() { } } + + private static class LowestTailPrecedenceTestBean extends TestBean implements TailOrdered { + + @Override + public int getOrder() { + return Ordered.LOWEST_PRECEDENCE; + } + } + + + private static class HighestTailPrecedenceTestBean extends TestBean implements TailOrdered { + + @Override + public int getOrder() { + return Ordered.HIGHEST_PRECEDENCE; + } + } + private static class Template { } diff --git a/spring-core/src/main/java/org/springframework/core/OrderComparator.java b/spring-core/src/main/java/org/springframework/core/OrderComparator.java index f184f6a460b4..8681b69dc47f 100644 --- a/spring-core/src/main/java/org/springframework/core/OrderComparator.java +++ b/spring-core/src/main/java/org/springframework/core/OrderComparator.java @@ -44,6 +44,7 @@ * * @author Juergen Hoeller * @author Sam Brannen + * @author Yanming Zhou * @since 07.04.2003 * @see Ordered * @see PriorityOrdered @@ -84,6 +85,15 @@ else if (p2 && !p1) { return 1; } + boolean t1 = (o1 instanceof TailOrdered); + boolean t2 = (o2 instanceof TailOrdered); + if (t1 && !t2) { + return 1; + } + else if (t2 && !t1) { + return -1; + } + int i1 = getOrder(o1, sourceProvider); int i2 = getOrder(o2, sourceProvider); return Integer.compare(i1, i2); diff --git a/spring-core/src/main/java/org/springframework/core/PriorityOrdered.java b/spring-core/src/main/java/org/springframework/core/PriorityOrdered.java index af8808e5ad65..497d1c1c72a7 100644 --- a/spring-core/src/main/java/org/springframework/core/PriorityOrdered.java +++ b/spring-core/src/main/java/org/springframework/core/PriorityOrdered.java @@ -41,6 +41,7 @@ * @author Juergen Hoeller * @author Sam Brannen * @since 2.5 + * @see TailOrdered * @see org.springframework.beans.factory.config.PropertyOverrideConfigurer */ public interface PriorityOrdered extends Ordered { diff --git a/spring-core/src/main/java/org/springframework/core/TailOrdered.java b/spring-core/src/main/java/org/springframework/core/TailOrdered.java new file mode 100644 index 000000000000..3222855a7852 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/core/TailOrdered.java @@ -0,0 +1,35 @@ +/* + * Copyright 2002-present 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. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.core; + +/** + * Extension of the {@link Ordered} interface, expressing a tail + * ordering: {@code TailOrdered} objects are always applied after + * plain {@link Ordered} objects regardless of their order values. + * + *

When sorting a set of {@code Ordered} objects, {@code TailOrdered} + * objects and plain {@code Ordered} objects are effectively treated as + * two separate subsets, with the set of {@code TailOrdered} objects following + * the set of plain {@code Ordered} objects and with relative + * ordering applied within those subsets. + * + * @author Yanming Zhou + * @since 7.1 + * @see PriorityOrdered + */ +public interface TailOrdered extends Ordered { +} diff --git a/spring-core/src/test/java/org/springframework/core/OrderComparatorTests.java b/spring-core/src/test/java/org/springframework/core/OrderComparatorTests.java index 7f124c460aaa..1cc3151d11fd 100644 --- a/spring-core/src/test/java/org/springframework/core/OrderComparatorTests.java +++ b/spring-core/src/test/java/org/springframework/core/OrderComparatorTests.java @@ -29,6 +29,7 @@ * @author Stephane Nicoll * @author Juergen Hoeller * @author Sam Brannen + * @author Yanming Zhou */ class OrderComparatorTests { @@ -105,6 +106,61 @@ private void assertThatPriorityOrderedAlwaysWins(StubPriorityOrdered priority, S assertThat(this.comparator.compare(standard, priority)).isEqualTo(1); } + @Test + void compareTailOrderedInstancesBefore() { + assertThat(this.comparator.compare(new StubTailOrdered(100), new StubTailOrdered(2000))).isEqualTo(-1); + } + + @Test + void compareTailOrderedInstancesSame() { + assertThat(this.comparator.compare(new StubTailOrdered(100), new StubTailOrdered(100))).isEqualTo(0); + } + + @Test + void compareTailOrderedInstancesAfter() { + assertThat(this.comparator.compare(new StubTailOrdered(982300), new StubTailOrdered(100))).isEqualTo(1); + } + + @Test + void compareTailOrderedInstanceToStandardOrderedInstanceWithHigherPriority() { + assertThatTailOrderedAlwaysLoses(new StubTailOrdered(200), new StubOrdered(100)); + } + + @Test + void compareTailOrderedInstanceToStandardOrderedInstanceWithSamePriority() { + assertThatTailOrderedAlwaysLoses(new StubTailOrdered(100), new StubOrdered(100)); + } + + @Test + void compareTailOrderedInstanceToStandardOrderedInstanceWithLowerPriority() { + assertThatTailOrderedAlwaysLoses(new StubTailOrdered(100), new StubOrdered(200)); + } + + private void assertThatTailOrderedAlwaysLoses(StubTailOrdered priority, StubOrdered standard) { + assertThat(this.comparator.compare(priority, standard)).isEqualTo(1); + assertThat(this.comparator.compare(standard, priority)).isEqualTo(-1); + } + + @Test + void compareTailOrderedInstanceToPriorityOrderedInstanceWithHigherPriority() { + assertThatTailOrderedAlwaysLoses(new StubTailOrdered(200), new StubPriorityOrdered(100)); + } + + @Test + void compareTailOrderedInstanceToPriorityOrderedInstanceWithSamePriority() { + assertThatTailOrderedAlwaysLoses(new StubTailOrdered(100), new StubPriorityOrdered(100)); + } + + @Test + void compareTailOrderedInstanceToPriorityOrderedInstanceWithLowerPriority() { + assertThatTailOrderedAlwaysLoses(new StubTailOrdered(100), new StubPriorityOrdered(200)); + } + + private void assertThatTailOrderedAlwaysLoses(StubTailOrdered priority, StubPriorityOrdered standard) { + assertThat(this.comparator.compare(priority, standard)).isEqualTo(1); + assertThat(this.comparator.compare(standard, priority)).isEqualTo(-1); + } + @Test void compareWithSimpleSourceProvider() { Comparator customComparator = this.comparator.withSourceProvider( @@ -162,6 +218,20 @@ public int getOrder() { } } + private static class StubTailOrdered implements TailOrdered { + + private final int order; + + StubTailOrdered(int order) { + this.order = order; + } + + @Override + public int getOrder() { + return this.order; + } + } + private static class TestSourceProvider implements OrderComparator.OrderSourceProvider { private final Object target;