Browse Source

Support Ordered interface for @ControllerAdvice beans

Closes gh-23163
pull/23189/head
Sam Brannen 7 years ago
parent
commit
9239ab1891
  1. 18
      spring-web/src/main/java/org/springframework/web/bind/annotation/ControllerAdvice.java
  2. 38
      spring-web/src/main/java/org/springframework/web/method/ControllerAdviceBean.java
  3. 10
      spring-web/src/test/java/org/springframework/web/method/ControllerAdviceBeanTests.java

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

@ -33,15 +33,15 @@ import org.springframework.stereotype.Component; @@ -33,15 +33,15 @@ import org.springframework.stereotype.Component;
* multiple {@code @Controller} classes.
*
* <p>Classes with {@code @ControllerAdvice} can be declared explicitly as Spring
* beans or auto-detected via classpath scanning. All such beans are sorted via
* {@link org.springframework.core.annotation.AnnotationAwareOrderComparator
* AnnotationAwareOrderComparator}, based on
* {@link org.springframework.core.annotation.Order @Order} and
* {@link org.springframework.core.Ordered Ordered}, and applied in that order
* at runtime. For handling exceptions, an {@code @ExceptionHandler} will be
* picked on the first advice with a matching exception handler method. For
* model attributes and {@code InitBinder} initialization, {@code @ModelAttribute}
* and {@code @InitBinder} methods will also follow {@code @ControllerAdvice} order.
* beans or auto-detected via classpath scanning. All such beans are sorted based
* on {@link org.springframework.core.Ordered Ordered},
* {@link org.springframework.core.annotation.Order @Order}, and
* {@link javax.annotation.Priority @Priority} (in that order of precedence),
* and applied in that order at runtime. For handling exceptions, an
* {@code @ExceptionHandler} will be picked on the first advice with a matching
* exception handler method. For model attributes and {@code InitBinder}
* initialization, {@code @ModelAttribute} and {@code @InitBinder} methods will
* also follow {@code @ControllerAdvice} order.
*
* <p>Note: For {@code @ExceptionHandler} methods, a root exception match will be
* preferred to just matching a cause of the current exception, among the handler

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

@ -67,7 +67,8 @@ public class ControllerAdviceBean implements Ordered { @@ -67,7 +67,8 @@ public class ControllerAdviceBean implements Ordered {
@Nullable
private final BeanFactory beanFactory;
private final int order;
@Nullable
private Integer order;
/**
@ -81,7 +82,6 @@ public class ControllerAdviceBean implements Ordered { @@ -81,7 +82,6 @@ public class ControllerAdviceBean implements Ordered {
this.beanType = ClassUtils.getUserClass(bean.getClass());
this.beanTypePredicate = createBeanTypePredicate(this.beanType);
this.beanFactory = null;
this.order = initOrderFromBean(bean);
}
/**
@ -117,16 +117,32 @@ public class ControllerAdviceBean implements Ordered { @@ -117,16 +117,32 @@ public class ControllerAdviceBean implements Ordered {
this.beanTypePredicate = (controllerAdvice != null ? createBeanTypePredicate(controllerAdvice)
: createBeanTypePredicate(this.beanType));
this.beanFactory = beanFactory;
this.order = initOrderFromBeanType(this.beanType);
}
/**
* Return the order value extracted from the {@link ControllerAdvice}
* annotation, or {@link Ordered#LOWEST_PRECEDENCE} otherwise.
* Get the order value for the contained bean.
* <p>As of Spring Framework 5.2, the order value is lazily retrieved using
* the following algorithm and cached.
* <ul>
* <li>If the {@linkplain #resolveBean resolved bean} implements {@link Ordered},
* use the value returned by {@link Ordered#getOrder()}.</li>
* <li>Otherwise use the value returned by {@link OrderUtils#getOrder(Class, int)}
* with {@link Ordered#LOWEST_PRECEDENCE} used as the default order value.</li>
* </ul>
* @see #resolveBean()
*/
@Override
public int getOrder() {
if (this.order == null) {
Object resolvedBean = resolveBean();
if (resolvedBean instanceof Ordered) {
this.order = ((Ordered) resolvedBean).getOrder();
}
else {
this.order = OrderUtils.getOrder(getBeanType(), Ordered.LOWEST_PRECEDENCE);
}
}
return this.order;
}
@ -236,16 +252,4 @@ public class ControllerAdviceBean implements Ordered { @@ -236,16 +252,4 @@ public class ControllerAdviceBean implements Ordered {
return HandlerTypePredicate.forAnyHandlerType();
}
private static int initOrderFromBean(Object bean) {
return (bean instanceof Ordered ? ((Ordered) bean).getOrder() : initOrderFromBeanType(bean.getClass()));
}
private static int initOrderFromBeanType(@Nullable Class<?> beanType) {
Integer order = null;
if (beanType != null) {
order = OrderUtils.getOrder(beanType);
}
return (order != null ? order : Ordered.LOWEST_PRECEDENCE);
}
}

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

@ -22,6 +22,7 @@ import javax.annotation.Priority; @@ -22,6 +22,7 @@ import javax.annotation.Priority;
import org.junit.Test;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
@ -32,7 +33,6 @@ import static org.assertj.core.api.Assertions.assertThat; @@ -32,7 +33,6 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
/**
@ -103,8 +103,7 @@ public class ControllerAdviceBeanTests { @@ -103,8 +103,7 @@ public class ControllerAdviceBeanTests {
@Test
public void orderedViaOrderedInterfaceForBeanName() {
// TODO Change expectedOrder once Ordered is properly supported
assertOrder(OrderedControllerAdvice.class, Ordered.LOWEST_PRECEDENCE);
assertOrder(OrderedControllerAdvice.class, 42);
}
@Test
@ -211,15 +210,14 @@ public class ControllerAdviceBeanTests { @@ -211,15 +210,14 @@ public class ControllerAdviceBeanTests {
BeanFactory beanFactory = mock(BeanFactory.class);
given(beanFactory.containsBean(beanName)).willReturn(true);
given(beanFactory.getType(beanName)).willReturn(beanType);
given(beanFactory.getBean(beanName)).willReturn(BeanUtils.instantiateClass(beanType));
ControllerAdviceBean controllerAdviceBean = new ControllerAdviceBean(beanName, beanFactory);
assertThat(controllerAdviceBean.getOrder()).isEqualTo(expectedOrder);
verify(beanFactory).containsBean(beanName);
verify(beanFactory).getType(beanName);
// Expecting 0 invocations of getBean() since Ordered is not yet supported.
// TODO Change expected number of invocations once Ordered is properly supported
verify(beanFactory, times(0)).getBean(beanName);
verify(beanFactory).getBean(beanName);
}
private void assertApplicable(String message, ControllerAdviceBean controllerAdvice, Class<?> controllerBeanType) {

Loading…
Cancel
Save