diff --git a/spring-web/src/main/java/org/springframework/web/method/HandlerMethod.java b/spring-web/src/main/java/org/springframework/web/method/HandlerMethod.java index bbcef56c8ba..90e93fd3642 100644 --- a/spring-web/src/main/java/org/springframework/web/method/HandlerMethod.java +++ b/spring-web/src/main/java/org/springframework/web/method/HandlerMethod.java @@ -102,11 +102,20 @@ public class HandlerMethod { * Create an instance from a bean instance and a method. */ public HandlerMethod(Object bean, Method method) { + this(bean, method, null); + } + + + /** + * Variant of {@link #HandlerMethod(Object, Method)} that + * also accepts a {@link MessageSource}. + */ + public HandlerMethod(Object bean, Method method, @Nullable MessageSource messageSource) { Assert.notNull(bean, "Bean is required"); Assert.notNull(method, "Method is required"); this.bean = bean; this.beanFactory = null; - this.messageSource = null; + this.messageSource = messageSource; this.beanType = ClassUtils.getUserClass(bean); this.method = method; this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(method); diff --git a/spring-web/src/main/java/org/springframework/web/method/support/InvocableHandlerMethod.java b/spring-web/src/main/java/org/springframework/web/method/support/InvocableHandlerMethod.java index b9c9a69c955..68d7b94d54d 100644 --- a/spring-web/src/main/java/org/springframework/web/method/support/InvocableHandlerMethod.java +++ b/spring-web/src/main/java/org/springframework/web/method/support/InvocableHandlerMethod.java @@ -20,6 +20,7 @@ import java.lang.reflect.Method; import java.util.Arrays; +import org.springframework.context.MessageSource; import org.springframework.core.CoroutinesUtils; import org.springframework.core.DefaultParameterNameDiscoverer; import org.springframework.core.KotlinDetector; @@ -71,6 +72,14 @@ public InvocableHandlerMethod(Object bean, Method method) { super(bean, method); } + /** + * Variant of {@link #InvocableHandlerMethod(Object, Method)} that + * also accepts a {@link MessageSource}. + */ + public InvocableHandlerMethod(Object bean, Method method, @Nullable MessageSource messageSource) { + super(bean, method, messageSource); + } + /** * Construct a new handler method with the given bean instance, method name and parameters. * @param bean the object bean diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolver.java index bdb29fc15a2..7be174e7a68 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolver.java @@ -483,7 +483,7 @@ protected ServletInvocableHandlerMethod getExceptionHandlerMethod( } Method method = resolver.resolveMethod(exception); if (method != null) { - return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method); + return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method, this.applicationContext); } // For advice applicability check below (involving base packages, assignable types // and annotation presence), use target class instead of interface-based proxy. @@ -498,7 +498,7 @@ protected ServletInvocableHandlerMethod getExceptionHandlerMethod( ExceptionHandlerMethodResolver resolver = entry.getValue(); Method method = resolver.resolveMethod(exception); if (method != null) { - return new ServletInvocableHandlerMethod(advice.resolveBean(), method); + return new ServletInvocableHandlerMethod(advice.resolveBean(), method, this.applicationContext); } } } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethod.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethod.java index 8cbdb10549d..88343c3dbfc 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethod.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletInvocableHandlerMethod.java @@ -25,6 +25,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.springframework.context.MessageSource; import org.springframework.core.KotlinDetector; import org.springframework.core.MethodParameter; import org.springframework.core.ResolvableType; @@ -68,6 +69,13 @@ public class ServletInvocableHandlerMethod extends InvocableHandlerMethod { @Nullable private HandlerMethodReturnValueHandlerComposite returnValueHandlers; + /** + * Variant of {@link #ServletInvocableHandlerMethod(Object, Method)} that + * also accepts a {@link MessageSource}. + */ + public ServletInvocableHandlerMethod(Object handler, Method method, @Nullable MessageSource messageSource) { + super(handler, method, messageSource); + } /** * Creates an instance from the given handler and method. diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolverTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolverTests.java index b3757c556eb..96093503626 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolverTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ExceptionHandlerExceptionResolverTests.java @@ -19,7 +19,9 @@ import java.io.IOException; import java.io.UnsupportedEncodingException; import java.io.Writer; +import java.net.SocketTimeoutException; import java.util.Collections; +import java.util.Locale; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; @@ -30,8 +32,11 @@ import org.springframework.context.annotation.AnnotationConfigApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.context.i18n.LocaleContextHolder; +import org.springframework.context.support.StaticApplicationContext; import org.springframework.core.MethodParameter; import org.springframework.core.annotation.Order; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.server.ServerHttpRequest; @@ -42,6 +47,7 @@ import org.springframework.web.HttpRequestHandler; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.context.support.WebApplicationObjectSupport; import org.springframework.web.method.HandlerMethod; @@ -333,6 +339,28 @@ void resolveExceptionWithAssertionErrorAsRootCause() throws Exception { assertThat(this.response.getContentAsString()).isEqualTo(rootCause.toString()); } + @Test //gh-27156 + void resolveExceptionWithReasonResovledByMessageSource() throws Exception { + AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MyConfig.class); + StaticApplicationContext context = new StaticApplicationContext(ctx); + Locale locale = Locale.ENGLISH; + context.addMessage("gateway.timeout", locale, "Gateway Timeout"); + context.refresh(); + LocaleContextHolder.setLocale(locale); + this.resolver.setApplicationContext(context); + this.resolver.afterPropertiesSet(); + + SocketTimeoutException ex = new SocketTimeoutException(); + HandlerMethod handlerMethod = new HandlerMethod(new ResponseBodyController(), "handle"); + ModelAndView mav = this.resolver.resolveException(this.request, this.response, handlerMethod, ex); + + assertThat(mav).as("Exception was not handled").isNotNull(); + assertThat(mav.isEmpty()).isTrue(); + assertThat(this.response.getStatus()).isEqualTo(HttpStatus.GATEWAY_TIMEOUT.value()); + assertThat(this.response.getErrorMessage()).isEqualTo("Gateway Timeout"); + assertThat(this.response.getContentAsString()).isEqualTo(""); + } + @Test void resolveExceptionControllerAdviceHandler() throws Exception { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(MyControllerAdviceConfig.class); @@ -530,6 +558,16 @@ public String handleException(Exception ex) { } } + @RestControllerAdvice + @Order(3) + static class ResponseStatusTestExceptionResolver { + + @ExceptionHandler(SocketTimeoutException.class) + @ResponseStatus(code = HttpStatus.GATEWAY_TIMEOUT, reason = "gateway.timeout") + public void handleException(SocketTimeoutException ex) { + + } + } @Configuration static class MyConfig { @@ -543,6 +581,11 @@ public TestExceptionResolver testExceptionResolver() { public AnotherTestExceptionResolver anotherTestExceptionResolver() { return new AnotherTestExceptionResolver(); } + + @Bean + public ResponseStatusTestExceptionResolver responseStatusTestExceptionResolver() { + return new ResponseStatusTestExceptionResolver(); + } }