Skip to content

Commit d0231cb

Browse files
committed
Presort beans in ControllerAdviceBean.findAnnotatedBeans()
Prior to this commit, all clients of ControllerAdviceBean.findAnnotatedBeans() sorted the returned list manually. In addition, clients within the core Spring Framework unnecessarily used AnnotationAwareOrderComparator instead of OrderComparator to sort the list. This commit presorts the ControllerAdviceBean list using OrderComparator directly within ControllerAdviceBean.findAnnotatedBeans(). Closes gh-23188
1 parent dd15ff7 commit d0231cb

File tree

7 files changed

+96
-32
lines changed

7 files changed

+96
-32
lines changed

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

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,17 +34,18 @@
3434
*
3535
* <p>Classes annotated with {@code @ControllerAdvice} can be declared explicitly
3636
* as Spring beans or auto-detected via classpath scanning. All such beans are
37-
* sorted based on {@link org.springframework.core.Ordered Ordered} /
38-
* {@link org.springframework.core.PriorityOrdered PriorityOrdered} semantics or
37+
* sorted based on {@link org.springframework.core.Ordered Ordered} semantics or
3938
* {@link org.springframework.core.annotation.Order @Order} /
40-
* {@link javax.annotation.Priority @Priority} declarations, with {@code Ordered} /
41-
* {@code PriorityOrdered} semantics taking precedence over {@code @Order} /
42-
* {@code @Priority} declarations. {@code @ControllerAdvice} beans are then
43-
* applied in that order at runtime. For handling exceptions, an
44-
* {@code @ExceptionHandler} will be picked on the first advice with a matching
45-
* exception handler method. For model attributes and {@code InitBinder}
46-
* initialization, {@code @ModelAttribute} and {@code @InitBinder} methods will
47-
* also follow {@code @ControllerAdvice} order.
39+
* {@link javax.annotation.Priority @Priority} declarations, with {@code Ordered}
40+
* semantics taking precedence over {@code @Order} / {@code @Priority} declarations.
41+
* {@code @ControllerAdvice} beans are then applied in that order at runtime.
42+
* Note, however, that {@code @ControllerAdvice} beans that implement
43+
* {@link org.springframework.core.PriorityOrdered PriorityOrdered} are <em>not</em>
44+
* given priority over {@code @ControllerAdvice} beans that implement {@code Ordered}.
45+
* For handling exceptions, an {@code @ExceptionHandler} will be picked on the
46+
* first advice with a matching exception handler method. For model attributes
47+
* and {@code InitBinder} initialization, {@code @ModelAttribute} and
48+
* {@code @InitBinder} methods will also follow {@code @ControllerAdvice} order.
4849
*
4950
* <p>Note: For {@code @ExceptionHandler} methods, a root exception match will be
5051
* 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: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.springframework.beans.factory.BeanFactory;
2323
import org.springframework.beans.factory.BeanFactoryUtils;
2424
import org.springframework.context.ApplicationContext;
25+
import org.springframework.core.OrderComparator;
2526
import org.springframework.core.Ordered;
2627
import org.springframework.core.annotation.AnnotatedElementUtils;
2728
import org.springframework.core.annotation.OrderUtils;
@@ -215,6 +216,11 @@ public String toString() {
215216
* Find beans annotated with {@link ControllerAdvice @ControllerAdvice} in the
216217
* given {@link ApplicationContext} and wrap them as {@code ControllerAdviceBean}
217218
* instances.
219+
* <p>As of Spring Framework 5.2, the {@code ControllerAdviceBean} instances
220+
* in the returned list are sorted using {@link OrderComparator#sort(List)}.
221+
* @see #getOrder()
222+
* @see OrderComparator
223+
* @see Ordered
218224
*/
219225
public static List<ControllerAdviceBean> findAnnotatedBeans(ApplicationContext context) {
220226
List<ControllerAdviceBean> adviceBeans = new ArrayList<>();
@@ -226,6 +232,7 @@ public static List<ControllerAdviceBean> findAnnotatedBeans(ApplicationContext c
226232
adviceBeans.add(new ControllerAdviceBean(name, context, controllerAdvice));
227233
}
228234
}
235+
OrderComparator.sort(adviceBeans);
229236
return adviceBeans;
230237
}
231238

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

Lines changed: 76 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,19 @@
1818

1919
import java.lang.annotation.Retention;
2020
import java.lang.annotation.RetentionPolicy;
21+
import java.util.List;
22+
2123
import javax.annotation.Priority;
2224

2325
import org.junit.Test;
2426

2527
import org.springframework.beans.BeanUtils;
2628
import org.springframework.beans.factory.BeanFactory;
29+
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
30+
import org.springframework.context.annotation.Bean;
31+
import org.springframework.context.annotation.Configuration;
2732
import org.springframework.core.Ordered;
33+
import org.springframework.core.PriorityOrdered;
2834
import org.springframework.core.annotation.Order;
2935
import org.springframework.web.bind.annotation.ControllerAdvice;
3036
import org.springframework.web.bind.annotation.RestController;
@@ -36,7 +42,7 @@
3642
import static org.mockito.Mockito.verify;
3743

3844
/**
39-
* Unit tests for {@link ControllerAdviceBean}.
45+
* Unit and integration tests for {@link ControllerAdviceBean}.
4046
*
4147
* @author Brian Clozel
4248
* @author Sam Brannen
@@ -113,14 +119,14 @@ public void orderedViaOrderedInterfaceForBeanInstance() {
113119

114120
@Test
115121
public void orderedViaAnnotationForBeanName() {
116-
assertOrder(OrderAnnotationControllerAdvice.class, 42);
117-
assertOrder(PriorityAnnotationControllerAdvice.class, 42);
122+
assertOrder(OrderAnnotationControllerAdvice.class, 100);
123+
assertOrder(PriorityAnnotationControllerAdvice.class, 200);
118124
}
119125

120126
@Test
121127
public void orderedViaAnnotationForBeanInstance() {
122-
assertOrder(new OrderAnnotationControllerAdvice(), 42);
123-
assertOrder(new PriorityAnnotationControllerAdvice(), 42);
128+
assertOrder(new OrderAnnotationControllerAdvice(), 100);
129+
assertOrder(new PriorityAnnotationControllerAdvice(), 200);
124130
}
125131

126132
@Test
@@ -192,6 +198,25 @@ public void multipleMatch() {
192198
assertNotApplicable("should not match", bean, InheritanceController.class);
193199
}
194200

201+
@Test
202+
@SuppressWarnings({ "rawtypes", "unchecked" })
203+
public void findAnnotatedBeansSortsBeans() {
204+
Class[] expectedTypes = {
205+
// Since ControllerAdviceBean currently treats PriorityOrdered the same as Ordered,
206+
// OrderedControllerAdvice is sorted before PriorityOrderedControllerAdvice.
207+
OrderedControllerAdvice.class,
208+
PriorityOrderedControllerAdvice.class,
209+
OrderAnnotationControllerAdvice.class,
210+
PriorityAnnotationControllerAdvice.class,
211+
SimpleControllerAdvice.class,
212+
};
213+
214+
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
215+
List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(context);
216+
217+
assertThat(adviceBeans).extracting(ControllerAdviceBean::getBeanType).containsExactly(expectedTypes);
218+
}
219+
195220
private void assertEqualsHashCodeAndToString(ControllerAdviceBean bean1, ControllerAdviceBean bean2, String toString) {
196221
assertThat(bean1).isEqualTo(bean2);
197222
assertThat(bean2).isEqualTo(bean1);
@@ -237,14 +262,17 @@ private void assertNotApplicable(String message, ControllerAdviceBean controller
237262
static class SimpleControllerAdvice {}
238263

239264
@ControllerAdvice
240-
@Order(42)
265+
@Order(100)
241266
static class OrderAnnotationControllerAdvice {}
242267

243268
@ControllerAdvice
244-
@Priority(42)
269+
@Priority(200)
245270
static class PriorityAnnotationControllerAdvice {}
246271

247272
@ControllerAdvice
273+
// @Order and @Priority should be ignored due to implementation of Ordered.
274+
@Order(100)
275+
@Priority(200)
248276
static class OrderedControllerAdvice implements Ordered {
249277

250278
@Override
@@ -253,6 +281,18 @@ public int getOrder() {
253281
}
254282
}
255283

284+
@ControllerAdvice
285+
// @Order and @Priority should be ignored due to implementation of PriorityOrdered.
286+
@Order(100)
287+
@Priority(200)
288+
static class PriorityOrderedControllerAdvice implements PriorityOrdered {
289+
290+
@Override
291+
public int getOrder() {
292+
return 55;
293+
}
294+
}
295+
256296
@ControllerAdvice(annotations = ControllerAnnotation.class)
257297
static class AnnotationSupport {}
258298

@@ -294,4 +334,33 @@ static abstract class AbstractController {}
294334

295335
static class InheritanceController extends AbstractController {}
296336

337+
@Configuration(proxyBeanMethods = false)
338+
static class Config {
339+
340+
@Bean
341+
SimpleControllerAdvice simpleControllerAdvice() {
342+
return new SimpleControllerAdvice();
343+
}
344+
345+
@Bean
346+
OrderAnnotationControllerAdvice orderAnnotationControllerAdvice() {
347+
return new OrderAnnotationControllerAdvice();
348+
}
349+
350+
@Bean
351+
PriorityAnnotationControllerAdvice priorityAnnotationControllerAdvice() {
352+
return new PriorityAnnotationControllerAdvice();
353+
}
354+
355+
@Bean
356+
OrderedControllerAdvice orderedControllerAdvice() {
357+
return new OrderedControllerAdvice();
358+
}
359+
360+
@Bean
361+
PriorityOrderedControllerAdvice priorityOrderedControllerAdvice() {
362+
return new PriorityOrderedControllerAdvice();
363+
}
364+
}
365+
297366
}

spring-webflux/src/main/java/org/springframework/web/reactive/result/method/annotation/ControllerMethodResolver.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@
3636
import org.springframework.core.MethodIntrospector;
3737
import org.springframework.core.ReactiveAdapterRegistry;
3838
import org.springframework.core.annotation.AnnotatedElementUtils;
39-
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
4039
import org.springframework.http.codec.HttpMessageReader;
4140
import org.springframework.lang.Nullable;
4241
import org.springframework.util.Assert;
@@ -223,8 +222,6 @@ private static List<HandlerMethodArgumentResolver> initResolvers(ArgumentResolve
223222

224223
private void initControllerAdviceCaches(ApplicationContext applicationContext) {
225224
List<ControllerAdviceBean> beans = ControllerAdviceBean.findAnnotatedBeans(applicationContext);
226-
AnnotationAwareOrderComparator.sort(beans);
227-
228225
for (ControllerAdviceBean bean : beans) {
229226
Class<?> beanType = bean.getBeanType();
230227
if (beanType != null) {

spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolver.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
import org.springframework.beans.factory.InitializingBean;
3232
import org.springframework.context.ApplicationContext;
3333
import org.springframework.context.ApplicationContextAware;
34-
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
3534
import org.springframework.http.HttpStatus;
3635
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
3736
import org.springframework.http.converter.HttpMessageConverter;
@@ -273,8 +272,6 @@ private void initExceptionHandlerAdviceCache() {
273272
}
274273

275274
List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
276-
AnnotationAwareOrderComparator.sort(adviceBeans);
277-
278275
for (ControllerAdviceBean adviceBean : adviceBeans) {
279276
Class<?> beanType = adviceBean.getBeanType();
280277
if (beanType == null) {

spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@
3737
import org.springframework.core.ParameterNameDiscoverer;
3838
import org.springframework.core.ReactiveAdapterRegistry;
3939
import org.springframework.core.annotation.AnnotatedElementUtils;
40-
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
4140
import org.springframework.core.log.LogFormatUtils;
4241
import org.springframework.core.task.AsyncTaskExecutor;
4342
import org.springframework.core.task.SimpleAsyncTaskExecutor;
@@ -578,7 +577,6 @@ private void initControllerAdviceCache() {
578577
}
579578

580579
List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
581-
AnnotationAwareOrderComparator.sort(adviceBeans);
582580

583581
List<Object> requestResponseBodyAdviceBeans = new ArrayList<>();
584582

spring-websocket/src/main/java/org/springframework/web/socket/messaging/WebSocketAnnotationMethodMessageHandler.java

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2019 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -20,7 +20,6 @@
2020
import java.util.List;
2121

2222
import org.springframework.context.ApplicationContext;
23-
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
2423
import org.springframework.lang.Nullable;
2524
import org.springframework.messaging.MessageChannel;
2625
import org.springframework.messaging.SubscribableChannel;
@@ -62,14 +61,10 @@ private void initControllerAdviceCache() {
6261
logger.trace("Looking for @MessageExceptionHandler mappings: " + context);
6362
}
6463
List<ControllerAdviceBean> beans = ControllerAdviceBean.findAnnotatedBeans(context);
65-
AnnotationAwareOrderComparator.sort(beans);
6664
initMessagingAdviceCache(MessagingControllerAdviceBean.createFromList(beans));
6765
}
6866

69-
private void initMessagingAdviceCache(@Nullable List<MessagingAdviceBean> beans) {
70-
if (beans == null) {
71-
return;
72-
}
67+
private void initMessagingAdviceCache(List<MessagingAdviceBean> beans) {
7368
for (MessagingAdviceBean bean : beans) {
7469
Class<?> type = bean.getBeanType();
7570
if (type != null) {

0 commit comments

Comments
 (0)