Skip to content

Commit 9239ab1

Browse files
committed
Support Ordered interface for @ControllerAdvice beans
Closes gh-23163
1 parent 978adbd commit 9239ab1

File tree

3 files changed

+34
-32
lines changed

3 files changed

+34
-32
lines changed

spring-web/src/main/java/org/springframework/web/bind/annotation/ControllerAdvice.java

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,15 @@
3333
* multiple {@code @Controller} classes.
3434
*
3535
* <p>Classes with {@code @ControllerAdvice} can be declared explicitly as Spring
36-
* beans or auto-detected via classpath scanning. All such beans are sorted via
37-
* {@link org.springframework.core.annotation.AnnotationAwareOrderComparator
38-
* AnnotationAwareOrderComparator}, based on
39-
* {@link org.springframework.core.annotation.Order @Order} and
40-
* {@link org.springframework.core.Ordered Ordered}, and applied in that order
41-
* at runtime. For handling exceptions, an {@code @ExceptionHandler} will be
42-
* picked on the first advice with a matching exception handler method. For
43-
* model attributes and {@code InitBinder} initialization, {@code @ModelAttribute}
44-
* and {@code @InitBinder} methods will also follow {@code @ControllerAdvice} order.
36+
* beans or auto-detected via classpath scanning. All such beans are sorted based
37+
* on {@link org.springframework.core.Ordered Ordered},
38+
* {@link org.springframework.core.annotation.Order @Order}, and
39+
* {@link javax.annotation.Priority @Priority} (in that order of precedence),
40+
* and applied in that order at runtime. For handling exceptions, an
41+
* {@code @ExceptionHandler} will be picked on the first advice with a matching
42+
* exception handler method. For model attributes and {@code InitBinder}
43+
* initialization, {@code @ModelAttribute} and {@code @InitBinder} methods will
44+
* also follow {@code @ControllerAdvice} order.
4545
*
4646
* <p>Note: For {@code @ExceptionHandler} methods, a root exception match will be
4747
* preferred to just matching a cause of the current exception, among the handler

spring-web/src/main/java/org/springframework/web/method/ControllerAdviceBean.java

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@ public class ControllerAdviceBean implements Ordered {
6767
@Nullable
6868
private final BeanFactory beanFactory;
6969

70-
private final int order;
70+
@Nullable
71+
private Integer order;
7172

7273

7374
/**
@@ -81,7 +82,6 @@ public ControllerAdviceBean(Object bean) {
8182
this.beanType = ClassUtils.getUserClass(bean.getClass());
8283
this.beanTypePredicate = createBeanTypePredicate(this.beanType);
8384
this.beanFactory = null;
84-
this.order = initOrderFromBean(bean);
8585
}
8686

8787
/**
@@ -117,16 +117,32 @@ public ControllerAdviceBean(String beanName, BeanFactory beanFactory, @Nullable
117117
this.beanTypePredicate = (controllerAdvice != null ? createBeanTypePredicate(controllerAdvice)
118118
: createBeanTypePredicate(this.beanType));
119119
this.beanFactory = beanFactory;
120-
this.order = initOrderFromBeanType(this.beanType);
121120
}
122121

123122

124123
/**
125-
* Return the order value extracted from the {@link ControllerAdvice}
126-
* annotation, or {@link Ordered#LOWEST_PRECEDENCE} otherwise.
124+
* Get the order value for the contained bean.
125+
* <p>As of Spring Framework 5.2, the order value is lazily retrieved using
126+
* the following algorithm and cached.
127+
* <ul>
128+
* <li>If the {@linkplain #resolveBean resolved bean} implements {@link Ordered},
129+
* use the value returned by {@link Ordered#getOrder()}.</li>
130+
* <li>Otherwise use the value returned by {@link OrderUtils#getOrder(Class, int)}
131+
* with {@link Ordered#LOWEST_PRECEDENCE} used as the default order value.</li>
132+
* </ul>
133+
* @see #resolveBean()
127134
*/
128135
@Override
129136
public int getOrder() {
137+
if (this.order == null) {
138+
Object resolvedBean = resolveBean();
139+
if (resolvedBean instanceof Ordered) {
140+
this.order = ((Ordered) resolvedBean).getOrder();
141+
}
142+
else {
143+
this.order = OrderUtils.getOrder(getBeanType(), Ordered.LOWEST_PRECEDENCE);
144+
}
145+
}
130146
return this.order;
131147
}
132148

@@ -236,16 +252,4 @@ private static HandlerTypePredicate createBeanTypePredicate(ControllerAdvice con
236252
return HandlerTypePredicate.forAnyHandlerType();
237253
}
238254

239-
private static int initOrderFromBean(Object bean) {
240-
return (bean instanceof Ordered ? ((Ordered) bean).getOrder() : initOrderFromBeanType(bean.getClass()));
241-
}
242-
243-
private static int initOrderFromBeanType(@Nullable Class<?> beanType) {
244-
Integer order = null;
245-
if (beanType != null) {
246-
order = OrderUtils.getOrder(beanType);
247-
}
248-
return (order != null ? order : Ordered.LOWEST_PRECEDENCE);
249-
}
250-
251255
}

spring-web/src/test/java/org/springframework/web/method/ControllerAdviceBeanTests.java

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
import org.junit.Test;
2424

25+
import org.springframework.beans.BeanUtils;
2526
import org.springframework.beans.factory.BeanFactory;
2627
import org.springframework.core.Ordered;
2728
import org.springframework.core.annotation.Order;
@@ -32,7 +33,6 @@
3233
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
3334
import static org.mockito.BDDMockito.given;
3435
import static org.mockito.Mockito.mock;
35-
import static org.mockito.Mockito.times;
3636
import static org.mockito.Mockito.verify;
3737

3838
/**
@@ -103,8 +103,7 @@ public void orderedWithLowestPrecedenceByDefaultForBeanInstance() {
103103

104104
@Test
105105
public void orderedViaOrderedInterfaceForBeanName() {
106-
// TODO Change expectedOrder once Ordered is properly supported
107-
assertOrder(OrderedControllerAdvice.class, Ordered.LOWEST_PRECEDENCE);
106+
assertOrder(OrderedControllerAdvice.class, 42);
108107
}
109108

110109
@Test
@@ -211,15 +210,14 @@ private void assertOrder(Class beanType, int expectedOrder) {
211210
BeanFactory beanFactory = mock(BeanFactory.class);
212211
given(beanFactory.containsBean(beanName)).willReturn(true);
213212
given(beanFactory.getType(beanName)).willReturn(beanType);
213+
given(beanFactory.getBean(beanName)).willReturn(BeanUtils.instantiateClass(beanType));
214214

215215
ControllerAdviceBean controllerAdviceBean = new ControllerAdviceBean(beanName, beanFactory);
216216

217217
assertThat(controllerAdviceBean.getOrder()).isEqualTo(expectedOrder);
218218
verify(beanFactory).containsBean(beanName);
219219
verify(beanFactory).getType(beanName);
220-
// Expecting 0 invocations of getBean() since Ordered is not yet supported.
221-
// TODO Change expected number of invocations once Ordered is properly supported
222-
verify(beanFactory, times(0)).getBean(beanName);
220+
verify(beanFactory).getBean(beanName);
223221
}
224222

225223
private void assertApplicable(String message, ControllerAdviceBean controllerAdvice, Class<?> controllerBeanType) {

0 commit comments

Comments
 (0)