diff --git a/spring-web/src/main/java/org/springframework/web/bind/annotation/ControllerAdvice.java b/spring-web/src/main/java/org/springframework/web/bind/annotation/ControllerAdvice.java
index 47ebcd6314873ca1079f3725632ebf047bb9800e..decfce3eb1af8691ca95b343dc5b122af7b0dce4 100644
--- a/spring-web/src/main/java/org/springframework/web/bind/annotation/ControllerAdvice.java
+++ b/spring-web/src/main/java/org/springframework/web/bind/annotation/ControllerAdvice.java
@@ -42,10 +42,12 @@ import org.springframework.stereotype.Component;
* Note, however, that {@code @ControllerAdvice} beans that implement
* {@link org.springframework.core.PriorityOrdered PriorityOrdered} are not
* given priority over {@code @ControllerAdvice} beans that implement {@code Ordered}.
- * 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.
+ * In addition, {@code Ordered} is not honored for scoped {@code @ControllerAdvice}
+ * beans — for example if such a bean has been configured as a request-scoped
+ * or session-scoped bean. For handling exceptions, an {@code @ExceptionHandler}
+ * will be picked on the first advice with a matching exception handler method. For
+ * model attributes and data binding initialization, {@code @ModelAttribute} and
+ * {@code @InitBinder} methods will follow {@code @ControllerAdvice} order.
*
*
Note: For {@code @ExceptionHandler} methods, a root exception match will be
* preferred to just matching a cause of the current exception, among the handler
diff --git a/spring-web/src/main/java/org/springframework/web/method/ControllerAdviceBean.java b/spring-web/src/main/java/org/springframework/web/method/ControllerAdviceBean.java
index 90b59765d9ac8721bb90183750aa15ba80d87856..8ec98e0ee7b0ee6d63375f3015e546694f39fb39 100644
--- a/spring-web/src/main/java/org/springframework/web/method/ControllerAdviceBean.java
+++ b/spring-web/src/main/java/org/springframework/web/method/ControllerAdviceBean.java
@@ -19,6 +19,7 @@ package org.springframework.web.method;
import java.util.ArrayList;
import java.util.List;
+import org.springframework.aop.scope.ScopedProxyUtils;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.context.ApplicationContext;
@@ -124,19 +125,42 @@ public class ControllerAdviceBean implements Ordered {
/**
* Get the order value for the contained bean.
*
As of Spring Framework 5.2, the order value is lazily retrieved using
- * the following algorithm and cached.
+ * the following algorithm and cached. Note, however, that a
+ * {@link ControllerAdvice @ControllerAdvice} bean that is configured as a
+ * scoped bean — for example, as a request-scoped or session-scoped
+ * bean — will not be eagerly resolved. Consequently, {@link Ordered} is
+ * not honored for scoped {@code @ControllerAdvice} beans.
*
* - If the {@linkplain #resolveBean resolved bean} implements {@link Ordered},
* use the value returned by {@link Ordered#getOrder()}.
- * - Otherwise use the value returned by {@link OrderUtils#getOrder(Class, int)}
- * with {@link Ordered#LOWEST_PRECEDENCE} used as the default order value.
+ * - If the {@linkplain #getBeanType() bean type} is known, use the value returned
+ * by {@link OrderUtils#getOrder(Class, int)} with {@link Ordered#LOWEST_PRECEDENCE}
+ * used as the default order value.
+ * - Otherwise use {@link Ordered#LOWEST_PRECEDENCE} as the default, fallback
+ * order value.
*
* @see #resolveBean()
*/
@Override
public int getOrder() {
if (this.order == null) {
- Object resolvedBean = resolveBean();
+ Object resolvedBean = null;
+ if (this.beanOrName instanceof String) {
+ String beanName = (String) this.beanOrName;
+ String targetBeanName = ScopedProxyUtils.getTargetBeanName(beanName);
+ boolean isScopedProxy = this.beanFactory.containsBean(targetBeanName);
+ // Avoid eager @ControllerAdvice bean resolution for scoped proxies,
+ // since attempting to do so during context initialization would result
+ // in an exception due to the current absence of the scope. For example,
+ // an HTTP request or session scope is not active during initialization.
+ if (!isScopedProxy && !ScopedProxyUtils.isScopedTarget(beanName)) {
+ resolvedBean = resolveBean();
+ }
+ }
+ else {
+ resolvedBean = resolveBean();
+ }
+
if (resolvedBean instanceof Ordered) {
this.order = ((Ordered) resolvedBean).getOrder();
}
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestScopedControllerAdviceIntegrationTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestScopedControllerAdviceIntegrationTests.java
new file mode 100644
index 0000000000000000000000000000000000000000..14b812987afa5d62289db26a7381eb15378dadbb
--- /dev/null
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestScopedControllerAdviceIntegrationTests.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2002-2019 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.web.servlet.mvc.method.annotation;
+
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+import org.springframework.mock.web.test.MockServletContext;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.context.annotation.RequestScope;
+import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
+import org.springframework.web.method.ControllerAdviceBean;
+import org.springframework.web.servlet.config.annotation.EnableWebMvc;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+
+/**
+ * Integration tests for request-scoped {@link ControllerAdvice @ControllerAdvice} beans.
+ *
+ * @author Sam Brannen
+ * @since 5.2.2
+ */
+class RequestScopedControllerAdviceIntegrationTests {
+
+ @Test // gh-23985
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ void loadContextWithRequestScopedControllerAdvice() {
+ AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
+ context.setServletContext(new MockServletContext());
+ context.register(Config.class);
+
+ assertThatCode(context::refresh).doesNotThrowAnyException();
+
+ // Until gh-24017 is fixed, we expect the RequestScopedControllerAdvice to show up twice.
+ Class[] expectedTypes = { RequestScopedControllerAdvice.class, RequestScopedControllerAdvice.class };
+
+ List adviceBeans = ControllerAdviceBean.findAnnotatedBeans(context);
+ assertThat(adviceBeans)//
+ .extracting(ControllerAdviceBean::getBeanType)//
+ .containsExactly(expectedTypes);
+
+ assertThat(adviceBeans)//
+ .extracting(ControllerAdviceBean::getOrder)//
+ .containsExactly(42, 42);
+
+ context.close();
+ }
+
+
+ @Configuration
+ @EnableWebMvc
+ static class Config {
+
+ @Bean
+ @RequestScope
+ RequestScopedControllerAdvice requestScopedControllerAdvice() {
+ return new RequestScopedControllerAdvice();
+ }
+ }
+
+ @ControllerAdvice
+ @Order(42)
+ static class RequestScopedControllerAdvice implements Ordered {
+
+ @Override
+ public int getOrder() {
+ return 99;
+ }
+ }
+
+}