From 7b894fe73bd585edc0a9eaa3c6013424040c8569 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Sat, 5 May 2018 12:46:58 +0200 Subject: [PATCH] ResponseEntityExceptionHandler rethrows unknown exception (for further processing in DispatcherServlet's HandlerExceptionResolver chain) Issue: SPR-16743 --- .../AbstractHandlerExceptionResolver.java | 6 +- .../ResponseEntityExceptionHandler.java | 110 +++++++++--------- .../DefaultHandlerExceptionResolver.java | 56 ++++----- .../ResponseEntityExceptionHandlerTests.java | 108 ++++++++++++++--- src/docs/asciidoc/web/webflux.adoc | 4 +- src/docs/asciidoc/web/webmvc.adoc | 2 +- 6 files changed, 184 insertions(+), 102 deletions(-) diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerExceptionResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerExceptionResolver.java index 997c786cad..d60a4a4adc 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerExceptionResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerExceptionResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 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. @@ -129,8 +129,8 @@ public abstract class AbstractHandlerExceptionResolver implements HandlerExcepti */ @Override @Nullable - public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, - @Nullable Object handler, Exception ex) { + public ModelAndView resolveException( + HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) { if (shouldApplyTo(request, handler)) { if (this.logger.isDebugEnabled()) { diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandler.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandler.java index c378356122..41c8d4aa3f 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandler.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandler.java @@ -70,7 +70,7 @@ import org.springframework.web.util.WebUtils; * using view resolution (e.g., via {@code ContentNegotiatingViewResolver}), * then {@code DefaultHandlerExceptionResolver} is good enough. * - *

Note that in order for an {@code @ControllerAdvice} sub-class to be + *

Note that in order for an {@code @ControllerAdvice} subclass to be * detected, {@link ExceptionHandlerExceptionResolver} must be configured. * * @author Rossen Stoyanchev @@ -121,8 +121,9 @@ public abstract class ResponseEntityExceptionHandler { AsyncRequestTimeoutException.class }) @Nullable - public final ResponseEntity handleException(Exception ex, WebRequest request) { + public final ResponseEntity handleException(Exception ex, WebRequest request) throws Exception { HttpHeaders headers = new HttpHeaders(); + if (ex instanceof HttpRequestMethodNotSupportedException) { HttpStatus status = HttpStatus.METHOD_NOT_ALLOWED; return handleHttpRequestMethodNotSupported((HttpRequestMethodNotSupportedException) ex, headers, status, request); @@ -181,38 +182,17 @@ public abstract class ResponseEntityExceptionHandler { } else if (ex instanceof AsyncRequestTimeoutException) { HttpStatus status = HttpStatus.SERVICE_UNAVAILABLE; - return handleAsyncRequestTimeoutException( - (AsyncRequestTimeoutException) ex, headers, status, request); + return handleAsyncRequestTimeoutException((AsyncRequestTimeoutException) ex, headers, status, request); } else { - if (logger.isWarnEnabled()) { - logger.warn("Unknown exception type: " + ex.getClass().getName()); - } - HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR; - return handleExceptionInternal(ex, null, headers, status, request); + // Unknown exception, typically a wrapper with a common MVC exception as cause + // (since @ExceptionHandler type declarations also match first-level causes): + // We only deal with top-level MVC exceptions here, so let's rethrow the given + // exception for further processing through the HandlerExceptionResolver chain. + throw ex; } } - /** - * A single place to customize the response body of all Exception types. - *

The default implementation sets the {@link WebUtils#ERROR_EXCEPTION_ATTRIBUTE} - * request attribute and creates a {@link ResponseEntity} from the given - * body, headers, and status. - * @param ex the exception - * @param body the body for the response - * @param headers the headers for the response - * @param status the response status - * @param request the current request - */ - protected ResponseEntity handleExceptionInternal(Exception ex, @Nullable Object body, - HttpHeaders headers, HttpStatus status, WebRequest request) { - - if (HttpStatus.INTERNAL_SERVER_ERROR.equals(status)) { - request.setAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE, ex, WebRequest.SCOPE_REQUEST); - } - return new ResponseEntity<>(body, headers, status); - } - /** * Customize the response for HttpRequestMethodNotSupportedException. *

This method logs a warning, sets the "Allow" header, and delegates to @@ -223,8 +203,8 @@ public abstract class ResponseEntityExceptionHandler { * @param request the current request * @return a {@code ResponseEntity} instance */ - protected ResponseEntity handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException ex, - HttpHeaders headers, HttpStatus status, WebRequest request) { + protected ResponseEntity handleHttpRequestMethodNotSupported( + HttpRequestMethodNotSupportedException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { pageNotFoundLogger.warn(ex.getMessage()); @@ -245,8 +225,8 @@ public abstract class ResponseEntityExceptionHandler { * @param request the current request * @return a {@code ResponseEntity} instance */ - protected ResponseEntity handleHttpMediaTypeNotSupported(HttpMediaTypeNotSupportedException ex, - HttpHeaders headers, HttpStatus status, WebRequest request) { + protected ResponseEntity handleHttpMediaTypeNotSupported( + HttpMediaTypeNotSupportedException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { List mediaTypes = ex.getSupportedMediaTypes(); if (!CollectionUtils.isEmpty(mediaTypes)) { @@ -265,8 +245,8 @@ public abstract class ResponseEntityExceptionHandler { * @param request the current request * @return a {@code ResponseEntity} instance */ - protected ResponseEntity handleHttpMediaTypeNotAcceptable(HttpMediaTypeNotAcceptableException ex, - HttpHeaders headers, HttpStatus status, WebRequest request) { + protected ResponseEntity handleHttpMediaTypeNotAcceptable( + HttpMediaTypeNotAcceptableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { return handleExceptionInternal(ex, null, headers, status, request); } @@ -281,8 +261,8 @@ public abstract class ResponseEntityExceptionHandler { * @return a {@code ResponseEntity} instance * @since 4.2 */ - protected ResponseEntity handleMissingPathVariable(MissingPathVariableException ex, - HttpHeaders headers, HttpStatus status, WebRequest request) { + protected ResponseEntity handleMissingPathVariable( + MissingPathVariableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { return handleExceptionInternal(ex, null, headers, status, request); } @@ -296,8 +276,8 @@ public abstract class ResponseEntityExceptionHandler { * @param request the current request * @return a {@code ResponseEntity} instance */ - protected ResponseEntity handleMissingServletRequestParameter(MissingServletRequestParameterException ex, - HttpHeaders headers, HttpStatus status, WebRequest request) { + protected ResponseEntity handleMissingServletRequestParameter( + MissingServletRequestParameterException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { return handleExceptionInternal(ex, null, headers, status, request); } @@ -311,8 +291,8 @@ public abstract class ResponseEntityExceptionHandler { * @param request the current request * @return a {@code ResponseEntity} instance */ - protected ResponseEntity handleServletRequestBindingException(ServletRequestBindingException ex, - HttpHeaders headers, HttpStatus status, WebRequest request) { + protected ResponseEntity handleServletRequestBindingException( + ServletRequestBindingException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { return handleExceptionInternal(ex, null, headers, status, request); } @@ -326,8 +306,8 @@ public abstract class ResponseEntityExceptionHandler { * @param request the current request * @return a {@code ResponseEntity} instance */ - protected ResponseEntity handleConversionNotSupported(ConversionNotSupportedException ex, - HttpHeaders headers, HttpStatus status, WebRequest request) { + protected ResponseEntity handleConversionNotSupported( + ConversionNotSupportedException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { return handleExceptionInternal(ex, null, headers, status, request); } @@ -341,8 +321,8 @@ public abstract class ResponseEntityExceptionHandler { * @param request the current request * @return a {@code ResponseEntity} instance */ - protected ResponseEntity handleTypeMismatch(TypeMismatchException ex, HttpHeaders headers, - HttpStatus status, WebRequest request) { + protected ResponseEntity handleTypeMismatch( + TypeMismatchException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { return handleExceptionInternal(ex, null, headers, status, request); } @@ -356,8 +336,8 @@ public abstract class ResponseEntityExceptionHandler { * @param request the current request * @return a {@code ResponseEntity} instance */ - protected ResponseEntity handleHttpMessageNotReadable(HttpMessageNotReadableException ex, - HttpHeaders headers, HttpStatus status, WebRequest request) { + protected ResponseEntity handleHttpMessageNotReadable( + HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { return handleExceptionInternal(ex, null, headers, status, request); } @@ -371,8 +351,8 @@ public abstract class ResponseEntityExceptionHandler { * @param request the current request * @return a {@code ResponseEntity} instance */ - protected ResponseEntity handleHttpMessageNotWritable(HttpMessageNotWritableException ex, - HttpHeaders headers, HttpStatus status, WebRequest request) { + protected ResponseEntity handleHttpMessageNotWritable( + HttpMessageNotWritableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { return handleExceptionInternal(ex, null, headers, status, request); } @@ -386,8 +366,8 @@ public abstract class ResponseEntityExceptionHandler { * @param request the current request * @return a {@code ResponseEntity} instance */ - protected ResponseEntity handleMethodArgumentNotValid(MethodArgumentNotValidException ex, - HttpHeaders headers, HttpStatus status, WebRequest request) { + protected ResponseEntity handleMethodArgumentNotValid( + MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { return handleExceptionInternal(ex, null, headers, status, request); } @@ -401,8 +381,8 @@ public abstract class ResponseEntityExceptionHandler { * @param request the current request * @return a {@code ResponseEntity} instance */ - protected ResponseEntity handleMissingServletRequestPart(MissingServletRequestPartException ex, - HttpHeaders headers, HttpStatus status, WebRequest request) { + protected ResponseEntity handleMissingServletRequestPart( + MissingServletRequestPartException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { return handleExceptionInternal(ex, null, headers, status, request); } @@ -416,8 +396,8 @@ public abstract class ResponseEntityExceptionHandler { * @param request the current request * @return a {@code ResponseEntity} instance */ - protected ResponseEntity handleBindException(BindException ex, HttpHeaders headers, - HttpStatus status, WebRequest request) { + protected ResponseEntity handleBindException( + BindException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { return handleExceptionInternal(ex, null, headers, status, request); } @@ -467,4 +447,24 @@ public abstract class ResponseEntityExceptionHandler { return handleExceptionInternal(ex, null, headers, status, webRequest); } + /** + * A single place to customize the response body of all Exception types. + *

The default implementation sets the {@link WebUtils#ERROR_EXCEPTION_ATTRIBUTE} + * request attribute and creates a {@link ResponseEntity} from the given + * body, headers, and status. + * @param ex the exception + * @param body the body for the response + * @param headers the headers for the response + * @param status the response status + * @param request the current request + */ + protected ResponseEntity handleExceptionInternal( + Exception ex, @Nullable Object body, HttpHeaders headers, HttpStatus status, WebRequest request) { + + if (HttpStatus.INTERNAL_SERVER_ERROR.equals(status)) { + request.setAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE, ex, WebRequest.SCOPE_REQUEST); + } + return new ResponseEntity<>(body, headers, status); + } + } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/DefaultHandlerExceptionResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/DefaultHandlerExceptionResolver.java index 61317a4430..82798f96cc 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/DefaultHandlerExceptionResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/DefaultHandlerExceptionResolver.java @@ -53,9 +53,9 @@ import org.springframework.web.servlet.NoHandlerFoundException; import org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver; /** - * Default implementation of the {@link org.springframework.web.servlet.HandlerExceptionResolver - * HandlerExceptionResolver} interface that resolves standard Spring exceptions and translates - * them to corresponding HTTP status codes. + * The default implementation of the {@link org.springframework.web.servlet.HandlerExceptionResolver} + * interface, resolving standard Spring MVC exceptions and translating them to corresponding + * HTTP status codes. * *

This exception resolver is enabled by default in the common Spring * {@link org.springframework.web.servlet.DispatcherServlet}. @@ -169,54 +169,59 @@ public class DefaultHandlerExceptionResolver extends AbstractHandlerExceptionRes try { if (ex instanceof HttpRequestMethodNotSupportedException) { - return handleHttpRequestMethodNotSupported((HttpRequestMethodNotSupportedException) ex, request, - response, handler); + return handleHttpRequestMethodNotSupported( + (HttpRequestMethodNotSupportedException) ex, request, response, handler); } else if (ex instanceof HttpMediaTypeNotSupportedException) { - return handleHttpMediaTypeNotSupported((HttpMediaTypeNotSupportedException) ex, request, response, - handler); + return handleHttpMediaTypeNotSupported( + (HttpMediaTypeNotSupportedException) ex, request, response, handler); } else if (ex instanceof HttpMediaTypeNotAcceptableException) { - return handleHttpMediaTypeNotAcceptable((HttpMediaTypeNotAcceptableException) ex, request, response, - handler); + return handleHttpMediaTypeNotAcceptable( + (HttpMediaTypeNotAcceptableException) ex, request, response, handler); } else if (ex instanceof MissingPathVariableException) { - return handleMissingPathVariable((MissingPathVariableException) ex, request, - response, handler); + return handleMissingPathVariable( + (MissingPathVariableException) ex, request, response, handler); } else if (ex instanceof MissingServletRequestParameterException) { - return handleMissingServletRequestParameter((MissingServletRequestParameterException) ex, request, - response, handler); + return handleMissingServletRequestParameter( + (MissingServletRequestParameterException) ex, request, response, handler); } else if (ex instanceof ServletRequestBindingException) { - return handleServletRequestBindingException((ServletRequestBindingException) ex, request, response, - handler); + return handleServletRequestBindingException( + (ServletRequestBindingException) ex, request, response, handler); } else if (ex instanceof ConversionNotSupportedException) { - return handleConversionNotSupported((ConversionNotSupportedException) ex, request, response, handler); + return handleConversionNotSupported( + (ConversionNotSupportedException) ex, request, response, handler); } else if (ex instanceof TypeMismatchException) { - return handleTypeMismatch((TypeMismatchException) ex, request, response, handler); + return handleTypeMismatch( + (TypeMismatchException) ex, request, response, handler); } else if (ex instanceof HttpMessageNotReadableException) { - return handleHttpMessageNotReadable((HttpMessageNotReadableException) ex, request, response, handler); + return handleHttpMessageNotReadable( + (HttpMessageNotReadableException) ex, request, response, handler); } else if (ex instanceof HttpMessageNotWritableException) { - return handleHttpMessageNotWritable((HttpMessageNotWritableException) ex, request, response, handler); + return handleHttpMessageNotWritable( + (HttpMessageNotWritableException) ex, request, response, handler); } else if (ex instanceof MethodArgumentNotValidException) { - return handleMethodArgumentNotValidException((MethodArgumentNotValidException) ex, request, response, - handler); + return handleMethodArgumentNotValidException( + (MethodArgumentNotValidException) ex, request, response, handler); } else if (ex instanceof MissingServletRequestPartException) { - return handleMissingServletRequestPartException((MissingServletRequestPartException) ex, request, - response, handler); + return handleMissingServletRequestPartException( + (MissingServletRequestPartException) ex, request, response, handler); } else if (ex instanceof BindException) { return handleBindException((BindException) ex, request, response, handler); } else if (ex instanceof NoHandlerFoundException) { - return handleNoHandlerFoundException((NoHandlerFoundException) ex, request, response, handler); + return handleNoHandlerFoundException( + (NoHandlerFoundException) ex, request, response, handler); } else if (ex instanceof AsyncRequestTimeoutException) { return handleAsyncRequestTimeoutException( @@ -225,7 +230,7 @@ public class DefaultHandlerExceptionResolver extends AbstractHandlerExceptionRes } catch (Exception handlerException) { if (logger.isWarnEnabled()) { - logger.warn("Handling of [" + ex.getClass().getName() + "] resulted in Exception", handlerException); + logger.warn("Handling of [" + ex.getClass().getName() + "] resulted in exception", handlerException); } } return null; @@ -550,7 +555,6 @@ public class DefaultHandlerExceptionResolver extends AbstractHandlerExceptionRes protected void sendServerError(Exception ex, HttpServletRequest request, HttpServletResponse response) throws IOException { - request.setAttribute("javax.servlet.error.exception", ex); response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandlerTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandlerTests.java index e6adfcc94e..b78e95b59d 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandlerTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandlerTests.java @@ -20,6 +20,7 @@ import java.lang.reflect.Method; import java.util.Arrays; import java.util.EnumSet; import java.util.List; +import javax.servlet.ServletException; import org.junit.Before; import org.junit.Test; @@ -38,6 +39,8 @@ import org.springframework.http.converter.HttpMessageNotWritableException; import org.springframework.http.server.ServletServerHttpRequest; import org.springframework.mock.web.test.MockHttpServletRequest; import org.springframework.mock.web.test.MockHttpServletResponse; +import org.springframework.mock.web.test.MockServletConfig; +import org.springframework.stereotype.Controller; import org.springframework.validation.BindException; import org.springframework.web.HttpMediaTypeNotAcceptableException; import org.springframework.web.HttpMediaTypeNotSupportedException; @@ -48,11 +51,13 @@ import org.springframework.web.bind.MissingServletRequestParameterException; import org.springframework.web.bind.ServletRequestBindingException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.context.request.ServletWebRequest; import org.springframework.web.context.request.WebRequest; import org.springframework.web.context.request.async.AsyncRequestTimeoutException; import org.springframework.web.context.support.StaticWebApplicationContext; import org.springframework.web.multipart.support.MissingServletRequestPartException; +import org.springframework.web.servlet.DispatcherServlet; import org.springframework.web.servlet.NoHandlerFoundException; import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver; @@ -86,9 +91,9 @@ public class ResponseEntityExceptionHandlerTests { this.defaultExceptionResolver = new DefaultHandlerExceptionResolver(); } + @Test public void supportsAllDefaultHandlerExceptionResolverExceptionTypes() throws Exception { - Class clazz = ResponseEntityExceptionHandler.class; Method handleExceptionMethod = clazz.getMethod("handleException", Exception.class, WebRequest.class); ExceptionHandler annotation = handleExceptionMethod.getAnnotation(ExceptionHandler.class); @@ -205,36 +210,109 @@ public class ResponseEntityExceptionHandlerTests { @Test public void controllerAdvice() throws Exception { - StaticWebApplicationContext cxt = new StaticWebApplicationContext(); - cxt.registerSingleton("exceptionHandler", ApplicationExceptionHandler.class); - cxt.refresh(); + StaticWebApplicationContext ctx = new StaticWebApplicationContext(); + ctx.registerSingleton("exceptionHandler", ApplicationExceptionHandler.class); + ctx.refresh(); ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver(); - resolver.setApplicationContext(cxt); + resolver.setApplicationContext(ctx); resolver.afterPropertiesSet(); ServletRequestBindingException ex = new ServletRequestBindingException("message"); - resolver.resolveException(this.servletRequest, this.servletResponse, null, ex); + assertNotNull(resolver.resolveException(this.servletRequest, this.servletResponse, null, ex)); + + assertEquals(400, this.servletResponse.getStatus()); + assertEquals("error content", this.servletResponse.getContentAsString()); + assertEquals("someHeaderValue", this.servletResponse.getHeader("someHeader")); + } + + @Test + public void controllerAdviceWithNestedException() { + StaticWebApplicationContext ctx = new StaticWebApplicationContext(); + ctx.registerSingleton("exceptionHandler", ApplicationExceptionHandler.class); + ctx.refresh(); + + ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver(); + resolver.setApplicationContext(ctx); + resolver.afterPropertiesSet(); + + IllegalStateException ex = new IllegalStateException(new ServletRequestBindingException("message")); + assertNull(resolver.resolveException(this.servletRequest, this.servletResponse, null, ex)); + } + + @Test + public void controllerAdviceWithinDispatcherServlet() throws Exception { + StaticWebApplicationContext ctx = new StaticWebApplicationContext(); + ctx.registerSingleton("controller", ExceptionThrowingController.class); + ctx.registerSingleton("exceptionHandler", ApplicationExceptionHandler.class); + ctx.refresh(); + + DispatcherServlet servlet = new DispatcherServlet(ctx); + servlet.init(new MockServletConfig()); + servlet.service(this.servletRequest, this.servletResponse); assertEquals(400, this.servletResponse.getStatus()); assertEquals("error content", this.servletResponse.getContentAsString()); assertEquals("someHeaderValue", this.servletResponse.getHeader("someHeader")); } + @Test + public void controllerAdviceWithNestedExceptionWithinDispatcherServlet() throws Exception { + StaticWebApplicationContext ctx = new StaticWebApplicationContext(); + ctx.registerSingleton("controller", NestedExceptionThrowingController.class); + ctx.registerSingleton("exceptionHandler", ApplicationExceptionHandler.class); + ctx.refresh(); + + DispatcherServlet servlet = new DispatcherServlet(ctx); + servlet.init(new MockServletConfig()); + try { + servlet.service(this.servletRequest, this.servletResponse); + } + catch (ServletException ex) { + assertTrue(ex.getCause() instanceof IllegalStateException); + assertTrue(ex.getCause().getCause() instanceof ServletRequestBindingException); + } + } + private ResponseEntity testException(Exception ex) { - ResponseEntity responseEntity = this.exceptionHandlerSupport.handleException(ex, this.request); + try { + ResponseEntity responseEntity = this.exceptionHandlerSupport.handleException(ex, this.request); + + // SPR-9653 + if (HttpStatus.INTERNAL_SERVER_ERROR.equals(responseEntity.getStatusCode())) { + assertSame(ex, this.servletRequest.getAttribute("javax.servlet.error.exception")); + } + + this.defaultExceptionResolver.resolveException(this.servletRequest, this.servletResponse, null, ex); + + assertEquals(this.servletResponse.getStatus(), responseEntity.getStatusCode().value()); - // SPR-9653 - if (HttpStatus.INTERNAL_SERVER_ERROR.equals(responseEntity.getStatusCode())) { - assertSame(ex, this.servletRequest.getAttribute("javax.servlet.error.exception")); + return responseEntity; } + catch (Exception ex2) { + throw new IllegalStateException("handleException threw exception", ex2); + } + } + + + @Controller + private static class ExceptionThrowingController { - this.defaultExceptionResolver.resolveException(this.servletRequest, this.servletResponse, null, ex); + @RequestMapping("/") + public void handleRequest() throws Exception { + throw new ServletRequestBindingException("message"); + } + } - assertEquals(this.servletResponse.getStatus(), responseEntity.getStatusCode().value()); - return responseEntity; + @Controller + private static class NestedExceptionThrowingController { + + @RequestMapping("/") + public void handleRequest() throws Exception { + throw new IllegalStateException(new ServletRequestBindingException("message")); + } } @@ -242,8 +320,8 @@ public class ResponseEntityExceptionHandlerTests { private static class ApplicationExceptionHandler extends ResponseEntityExceptionHandler { @Override - protected ResponseEntity handleServletRequestBindingException(ServletRequestBindingException ex, - HttpHeaders headers, HttpStatus status, WebRequest request) { + protected ResponseEntity handleServletRequestBindingException( + ServletRequestBindingException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { headers.set("someHeader", "someHeaderValue"); return handleExceptionInternal(ex, "error content", headers, status, request); diff --git a/src/docs/asciidoc/web/webflux.adoc b/src/docs/asciidoc/web/webflux.adoc index c76b839351..69182744f5 100644 --- a/src/docs/asciidoc/web/webflux.adoc +++ b/src/docs/asciidoc/web/webflux.adoc @@ -2448,8 +2448,8 @@ in `@ControllerAdvice` classes to apply them globally. ==== Note that Spring WebFlux does not have an equivalent for the Spring MVC `ResponseEntityExceptionHandler` because WebFlux only raises `ResponseStatusException` -(or sub-classes of), which and those do not need to be translated translation to an HTTP -status code. +(or subclasses thereof), which and those do not need to be translated translation to +an HTTP status code. ==== diff --git a/src/docs/asciidoc/web/webmvc.adoc b/src/docs/asciidoc/web/webmvc.adoc index ca449b61a7..55f924e968 100644 --- a/src/docs/asciidoc/web/webmvc.adoc +++ b/src/docs/asciidoc/web/webmvc.adoc @@ -2977,7 +2977,7 @@ Applications that implement global exception handling with error details in the body should consider extending {api-spring-framework}/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandler.html[ResponseEntityExceptionHandler] which provides handling for exceptions that Spring MVC raises along with hooks to -customize the response body. To make use of this, create a sub-class of +customize the response body. To make use of this, create a subclass of `ResponseEntityExceptionHandler`, annotate with `@ControllerAdvice`, override the necessary methods, and declare it as a Spring bean. -- GitLab