Browse Source

ResponseEntityExceptionHandler rethrows unknown exception (for further processing in DispatcherServlet's HandlerExceptionResolver chain)

Issue: SPR-16743
pull/1820/head
Juergen Hoeller 8 years ago
parent
commit
7b894fe73b
  1. 6
      spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerExceptionResolver.java
  2. 110
      spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandler.java
  3. 56
      spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/support/DefaultHandlerExceptionResolver.java
  4. 108
      spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandlerTests.java
  5. 4
      src/docs/asciidoc/web/webflux.adoc
  6. 2
      src/docs/asciidoc/web/webmvc.adoc

6
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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 @Override
@Nullable @Nullable
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, public ModelAndView resolveException(
@Nullable Object handler, Exception ex) { HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
if (shouldApplyTo(request, handler)) { if (shouldApplyTo(request, handler)) {
if (this.logger.isDebugEnabled()) { if (this.logger.isDebugEnabled()) {

110
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}), * using view resolution (e.g., via {@code ContentNegotiatingViewResolver}),
* then {@code DefaultHandlerExceptionResolver} is good enough. * then {@code DefaultHandlerExceptionResolver} is good enough.
* *
* <p>Note that in order for an {@code @ControllerAdvice} sub-class to be * <p>Note that in order for an {@code @ControllerAdvice} subclass to be
* detected, {@link ExceptionHandlerExceptionResolver} must be configured. * detected, {@link ExceptionHandlerExceptionResolver} must be configured.
* *
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
@ -121,8 +121,9 @@ public abstract class ResponseEntityExceptionHandler {
AsyncRequestTimeoutException.class AsyncRequestTimeoutException.class
}) })
@Nullable @Nullable
public final ResponseEntity<Object> handleException(Exception ex, WebRequest request) { public final ResponseEntity<Object> handleException(Exception ex, WebRequest request) throws Exception {
HttpHeaders headers = new HttpHeaders(); HttpHeaders headers = new HttpHeaders();
if (ex instanceof HttpRequestMethodNotSupportedException) { if (ex instanceof HttpRequestMethodNotSupportedException) {
HttpStatus status = HttpStatus.METHOD_NOT_ALLOWED; HttpStatus status = HttpStatus.METHOD_NOT_ALLOWED;
return handleHttpRequestMethodNotSupported((HttpRequestMethodNotSupportedException) ex, headers, status, request); return handleHttpRequestMethodNotSupported((HttpRequestMethodNotSupportedException) ex, headers, status, request);
@ -181,38 +182,17 @@ public abstract class ResponseEntityExceptionHandler {
} }
else if (ex instanceof AsyncRequestTimeoutException) { else if (ex instanceof AsyncRequestTimeoutException) {
HttpStatus status = HttpStatus.SERVICE_UNAVAILABLE; HttpStatus status = HttpStatus.SERVICE_UNAVAILABLE;
return handleAsyncRequestTimeoutException( return handleAsyncRequestTimeoutException((AsyncRequestTimeoutException) ex, headers, status, request);
(AsyncRequestTimeoutException) ex, headers, status, request);
} }
else { else {
if (logger.isWarnEnabled()) { // Unknown exception, typically a wrapper with a common MVC exception as cause
logger.warn("Unknown exception type: " + ex.getClass().getName()); // (since @ExceptionHandler type declarations also match first-level causes):
} // We only deal with top-level MVC exceptions here, so let's rethrow the given
HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR; // exception for further processing through the HandlerExceptionResolver chain.
return handleExceptionInternal(ex, null, headers, status, request); throw ex;
} }
} }
/**
* A single place to customize the response body of all Exception types.
* <p>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<Object> 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. * Customize the response for HttpRequestMethodNotSupportedException.
* <p>This method logs a warning, sets the "Allow" header, and delegates to * <p>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 * @param request the current request
* @return a {@code ResponseEntity} instance * @return a {@code ResponseEntity} instance
*/ */
protected ResponseEntity<Object> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException ex, protected ResponseEntity<Object> handleHttpRequestMethodNotSupported(
HttpHeaders headers, HttpStatus status, WebRequest request) { HttpRequestMethodNotSupportedException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
pageNotFoundLogger.warn(ex.getMessage()); pageNotFoundLogger.warn(ex.getMessage());
@ -245,8 +225,8 @@ public abstract class ResponseEntityExceptionHandler {
* @param request the current request * @param request the current request
* @return a {@code ResponseEntity} instance * @return a {@code ResponseEntity} instance
*/ */
protected ResponseEntity<Object> handleHttpMediaTypeNotSupported(HttpMediaTypeNotSupportedException ex, protected ResponseEntity<Object> handleHttpMediaTypeNotSupported(
HttpHeaders headers, HttpStatus status, WebRequest request) { HttpMediaTypeNotSupportedException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
List<MediaType> mediaTypes = ex.getSupportedMediaTypes(); List<MediaType> mediaTypes = ex.getSupportedMediaTypes();
if (!CollectionUtils.isEmpty(mediaTypes)) { if (!CollectionUtils.isEmpty(mediaTypes)) {
@ -265,8 +245,8 @@ public abstract class ResponseEntityExceptionHandler {
* @param request the current request * @param request the current request
* @return a {@code ResponseEntity} instance * @return a {@code ResponseEntity} instance
*/ */
protected ResponseEntity<Object> handleHttpMediaTypeNotAcceptable(HttpMediaTypeNotAcceptableException ex, protected ResponseEntity<Object> handleHttpMediaTypeNotAcceptable(
HttpHeaders headers, HttpStatus status, WebRequest request) { HttpMediaTypeNotAcceptableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
return handleExceptionInternal(ex, null, headers, status, request); return handleExceptionInternal(ex, null, headers, status, request);
} }
@ -281,8 +261,8 @@ public abstract class ResponseEntityExceptionHandler {
* @return a {@code ResponseEntity} instance * @return a {@code ResponseEntity} instance
* @since 4.2 * @since 4.2
*/ */
protected ResponseEntity<Object> handleMissingPathVariable(MissingPathVariableException ex, protected ResponseEntity<Object> handleMissingPathVariable(
HttpHeaders headers, HttpStatus status, WebRequest request) { MissingPathVariableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
return handleExceptionInternal(ex, null, headers, status, request); return handleExceptionInternal(ex, null, headers, status, request);
} }
@ -296,8 +276,8 @@ public abstract class ResponseEntityExceptionHandler {
* @param request the current request * @param request the current request
* @return a {@code ResponseEntity} instance * @return a {@code ResponseEntity} instance
*/ */
protected ResponseEntity<Object> handleMissingServletRequestParameter(MissingServletRequestParameterException ex, protected ResponseEntity<Object> handleMissingServletRequestParameter(
HttpHeaders headers, HttpStatus status, WebRequest request) { MissingServletRequestParameterException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
return handleExceptionInternal(ex, null, headers, status, request); return handleExceptionInternal(ex, null, headers, status, request);
} }
@ -311,8 +291,8 @@ public abstract class ResponseEntityExceptionHandler {
* @param request the current request * @param request the current request
* @return a {@code ResponseEntity} instance * @return a {@code ResponseEntity} instance
*/ */
protected ResponseEntity<Object> handleServletRequestBindingException(ServletRequestBindingException ex, protected ResponseEntity<Object> handleServletRequestBindingException(
HttpHeaders headers, HttpStatus status, WebRequest request) { ServletRequestBindingException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
return handleExceptionInternal(ex, null, headers, status, request); return handleExceptionInternal(ex, null, headers, status, request);
} }
@ -326,8 +306,8 @@ public abstract class ResponseEntityExceptionHandler {
* @param request the current request * @param request the current request
* @return a {@code ResponseEntity} instance * @return a {@code ResponseEntity} instance
*/ */
protected ResponseEntity<Object> handleConversionNotSupported(ConversionNotSupportedException ex, protected ResponseEntity<Object> handleConversionNotSupported(
HttpHeaders headers, HttpStatus status, WebRequest request) { ConversionNotSupportedException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
return handleExceptionInternal(ex, null, headers, status, request); return handleExceptionInternal(ex, null, headers, status, request);
} }
@ -341,8 +321,8 @@ public abstract class ResponseEntityExceptionHandler {
* @param request the current request * @param request the current request
* @return a {@code ResponseEntity} instance * @return a {@code ResponseEntity} instance
*/ */
protected ResponseEntity<Object> handleTypeMismatch(TypeMismatchException ex, HttpHeaders headers, protected ResponseEntity<Object> handleTypeMismatch(
HttpStatus status, WebRequest request) { TypeMismatchException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
return handleExceptionInternal(ex, null, headers, status, request); return handleExceptionInternal(ex, null, headers, status, request);
} }
@ -356,8 +336,8 @@ public abstract class ResponseEntityExceptionHandler {
* @param request the current request * @param request the current request
* @return a {@code ResponseEntity} instance * @return a {@code ResponseEntity} instance
*/ */
protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex, protected ResponseEntity<Object> handleHttpMessageNotReadable(
HttpHeaders headers, HttpStatus status, WebRequest request) { HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
return handleExceptionInternal(ex, null, headers, status, request); return handleExceptionInternal(ex, null, headers, status, request);
} }
@ -371,8 +351,8 @@ public abstract class ResponseEntityExceptionHandler {
* @param request the current request * @param request the current request
* @return a {@code ResponseEntity} instance * @return a {@code ResponseEntity} instance
*/ */
protected ResponseEntity<Object> handleHttpMessageNotWritable(HttpMessageNotWritableException ex, protected ResponseEntity<Object> handleHttpMessageNotWritable(
HttpHeaders headers, HttpStatus status, WebRequest request) { HttpMessageNotWritableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
return handleExceptionInternal(ex, null, headers, status, request); return handleExceptionInternal(ex, null, headers, status, request);
} }
@ -386,8 +366,8 @@ public abstract class ResponseEntityExceptionHandler {
* @param request the current request * @param request the current request
* @return a {@code ResponseEntity} instance * @return a {@code ResponseEntity} instance
*/ */
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, protected ResponseEntity<Object> handleMethodArgumentNotValid(
HttpHeaders headers, HttpStatus status, WebRequest request) { MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
return handleExceptionInternal(ex, null, headers, status, request); return handleExceptionInternal(ex, null, headers, status, request);
} }
@ -401,8 +381,8 @@ public abstract class ResponseEntityExceptionHandler {
* @param request the current request * @param request the current request
* @return a {@code ResponseEntity} instance * @return a {@code ResponseEntity} instance
*/ */
protected ResponseEntity<Object> handleMissingServletRequestPart(MissingServletRequestPartException ex, protected ResponseEntity<Object> handleMissingServletRequestPart(
HttpHeaders headers, HttpStatus status, WebRequest request) { MissingServletRequestPartException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
return handleExceptionInternal(ex, null, headers, status, request); return handleExceptionInternal(ex, null, headers, status, request);
} }
@ -416,8 +396,8 @@ public abstract class ResponseEntityExceptionHandler {
* @param request the current request * @param request the current request
* @return a {@code ResponseEntity} instance * @return a {@code ResponseEntity} instance
*/ */
protected ResponseEntity<Object> handleBindException(BindException ex, HttpHeaders headers, protected ResponseEntity<Object> handleBindException(
HttpStatus status, WebRequest request) { BindException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
return handleExceptionInternal(ex, null, headers, status, request); return handleExceptionInternal(ex, null, headers, status, request);
} }
@ -467,4 +447,24 @@ public abstract class ResponseEntityExceptionHandler {
return handleExceptionInternal(ex, null, headers, status, webRequest); return handleExceptionInternal(ex, null, headers, status, webRequest);
} }
/**
* A single place to customize the response body of all Exception types.
* <p>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<Object> 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);
}
} }

56
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; import org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver;
/** /**
* Default implementation of the {@link org.springframework.web.servlet.HandlerExceptionResolver * The default implementation of the {@link org.springframework.web.servlet.HandlerExceptionResolver}
* HandlerExceptionResolver} interface that resolves standard Spring exceptions and translates * interface, resolving standard Spring MVC exceptions and translating them to corresponding
* them to corresponding HTTP status codes. * HTTP status codes.
* *
* <p>This exception resolver is enabled by default in the common Spring * <p>This exception resolver is enabled by default in the common Spring
* {@link org.springframework.web.servlet.DispatcherServlet}. * {@link org.springframework.web.servlet.DispatcherServlet}.
@ -169,54 +169,59 @@ public class DefaultHandlerExceptionResolver extends AbstractHandlerExceptionRes
try { try {
if (ex instanceof HttpRequestMethodNotSupportedException) { if (ex instanceof HttpRequestMethodNotSupportedException) {
return handleHttpRequestMethodNotSupported((HttpRequestMethodNotSupportedException) ex, request, return handleHttpRequestMethodNotSupported(
response, handler); (HttpRequestMethodNotSupportedException) ex, request, response, handler);
} }
else if (ex instanceof HttpMediaTypeNotSupportedException) { else if (ex instanceof HttpMediaTypeNotSupportedException) {
return handleHttpMediaTypeNotSupported((HttpMediaTypeNotSupportedException) ex, request, response, return handleHttpMediaTypeNotSupported(
handler); (HttpMediaTypeNotSupportedException) ex, request, response, handler);
} }
else if (ex instanceof HttpMediaTypeNotAcceptableException) { else if (ex instanceof HttpMediaTypeNotAcceptableException) {
return handleHttpMediaTypeNotAcceptable((HttpMediaTypeNotAcceptableException) ex, request, response, return handleHttpMediaTypeNotAcceptable(
handler); (HttpMediaTypeNotAcceptableException) ex, request, response, handler);
} }
else if (ex instanceof MissingPathVariableException) { else if (ex instanceof MissingPathVariableException) {
return handleMissingPathVariable((MissingPathVariableException) ex, request, return handleMissingPathVariable(
response, handler); (MissingPathVariableException) ex, request, response, handler);
} }
else if (ex instanceof MissingServletRequestParameterException) { else if (ex instanceof MissingServletRequestParameterException) {
return handleMissingServletRequestParameter((MissingServletRequestParameterException) ex, request, return handleMissingServletRequestParameter(
response, handler); (MissingServletRequestParameterException) ex, request, response, handler);
} }
else if (ex instanceof ServletRequestBindingException) { else if (ex instanceof ServletRequestBindingException) {
return handleServletRequestBindingException((ServletRequestBindingException) ex, request, response, return handleServletRequestBindingException(
handler); (ServletRequestBindingException) ex, request, response, handler);
} }
else if (ex instanceof ConversionNotSupportedException) { else if (ex instanceof ConversionNotSupportedException) {
return handleConversionNotSupported((ConversionNotSupportedException) ex, request, response, handler); return handleConversionNotSupported(
(ConversionNotSupportedException) ex, request, response, handler);
} }
else if (ex instanceof TypeMismatchException) { else if (ex instanceof TypeMismatchException) {
return handleTypeMismatch((TypeMismatchException) ex, request, response, handler); return handleTypeMismatch(
(TypeMismatchException) ex, request, response, handler);
} }
else if (ex instanceof HttpMessageNotReadableException) { else if (ex instanceof HttpMessageNotReadableException) {
return handleHttpMessageNotReadable((HttpMessageNotReadableException) ex, request, response, handler); return handleHttpMessageNotReadable(
(HttpMessageNotReadableException) ex, request, response, handler);
} }
else if (ex instanceof HttpMessageNotWritableException) { else if (ex instanceof HttpMessageNotWritableException) {
return handleHttpMessageNotWritable((HttpMessageNotWritableException) ex, request, response, handler); return handleHttpMessageNotWritable(
(HttpMessageNotWritableException) ex, request, response, handler);
} }
else if (ex instanceof MethodArgumentNotValidException) { else if (ex instanceof MethodArgumentNotValidException) {
return handleMethodArgumentNotValidException((MethodArgumentNotValidException) ex, request, response, return handleMethodArgumentNotValidException(
handler); (MethodArgumentNotValidException) ex, request, response, handler);
} }
else if (ex instanceof MissingServletRequestPartException) { else if (ex instanceof MissingServletRequestPartException) {
return handleMissingServletRequestPartException((MissingServletRequestPartException) ex, request, return handleMissingServletRequestPartException(
response, handler); (MissingServletRequestPartException) ex, request, response, handler);
} }
else if (ex instanceof BindException) { else if (ex instanceof BindException) {
return handleBindException((BindException) ex, request, response, handler); return handleBindException((BindException) ex, request, response, handler);
} }
else if (ex instanceof NoHandlerFoundException) { else if (ex instanceof NoHandlerFoundException) {
return handleNoHandlerFoundException((NoHandlerFoundException) ex, request, response, handler); return handleNoHandlerFoundException(
(NoHandlerFoundException) ex, request, response, handler);
} }
else if (ex instanceof AsyncRequestTimeoutException) { else if (ex instanceof AsyncRequestTimeoutException) {
return handleAsyncRequestTimeoutException( return handleAsyncRequestTimeoutException(
@ -225,7 +230,7 @@ public class DefaultHandlerExceptionResolver extends AbstractHandlerExceptionRes
} }
catch (Exception handlerException) { catch (Exception handlerException) {
if (logger.isWarnEnabled()) { 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; return null;
@ -550,7 +555,6 @@ public class DefaultHandlerExceptionResolver extends AbstractHandlerExceptionRes
protected void sendServerError(Exception ex, HttpServletRequest request, HttpServletResponse response) protected void sendServerError(Exception ex, HttpServletRequest request, HttpServletResponse response)
throws IOException { throws IOException {
request.setAttribute("javax.servlet.error.exception", ex); request.setAttribute("javax.servlet.error.exception", ex);
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
} }

108
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.Arrays;
import java.util.EnumSet; import java.util.EnumSet;
import java.util.List; import java.util.List;
import javax.servlet.ServletException;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@ -38,6 +39,8 @@ import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.http.server.ServletServerHttpRequest; import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.mock.web.test.MockHttpServletRequest; import org.springframework.mock.web.test.MockHttpServletRequest;
import org.springframework.mock.web.test.MockHttpServletResponse; 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.validation.BindException;
import org.springframework.web.HttpMediaTypeNotAcceptableException; import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpMediaTypeNotSupportedException; 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.ServletRequestBindingException;
import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler; 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.ServletWebRequest;
import org.springframework.web.context.request.WebRequest; import org.springframework.web.context.request.WebRequest;
import org.springframework.web.context.request.async.AsyncRequestTimeoutException; import org.springframework.web.context.request.async.AsyncRequestTimeoutException;
import org.springframework.web.context.support.StaticWebApplicationContext; import org.springframework.web.context.support.StaticWebApplicationContext;
import org.springframework.web.multipart.support.MissingServletRequestPartException; import org.springframework.web.multipart.support.MissingServletRequestPartException;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.NoHandlerFoundException; import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver; import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver;
@ -86,9 +91,9 @@ public class ResponseEntityExceptionHandlerTests {
this.defaultExceptionResolver = new DefaultHandlerExceptionResolver(); this.defaultExceptionResolver = new DefaultHandlerExceptionResolver();
} }
@Test @Test
public void supportsAllDefaultHandlerExceptionResolverExceptionTypes() throws Exception { public void supportsAllDefaultHandlerExceptionResolverExceptionTypes() throws Exception {
Class<ResponseEntityExceptionHandler> clazz = ResponseEntityExceptionHandler.class; Class<ResponseEntityExceptionHandler> clazz = ResponseEntityExceptionHandler.class;
Method handleExceptionMethod = clazz.getMethod("handleException", Exception.class, WebRequest.class); Method handleExceptionMethod = clazz.getMethod("handleException", Exception.class, WebRequest.class);
ExceptionHandler annotation = handleExceptionMethod.getAnnotation(ExceptionHandler.class); ExceptionHandler annotation = handleExceptionMethod.getAnnotation(ExceptionHandler.class);
@ -205,36 +210,109 @@ public class ResponseEntityExceptionHandlerTests {
@Test @Test
public void controllerAdvice() throws Exception { public void controllerAdvice() throws Exception {
StaticWebApplicationContext cxt = new StaticWebApplicationContext(); StaticWebApplicationContext ctx = new StaticWebApplicationContext();
cxt.registerSingleton("exceptionHandler", ApplicationExceptionHandler.class); ctx.registerSingleton("exceptionHandler", ApplicationExceptionHandler.class);
cxt.refresh(); ctx.refresh();
ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver(); ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver();
resolver.setApplicationContext(cxt); resolver.setApplicationContext(ctx);
resolver.afterPropertiesSet(); resolver.afterPropertiesSet();
ServletRequestBindingException ex = new ServletRequestBindingException("message"); 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(400, this.servletResponse.getStatus());
assertEquals("error content", this.servletResponse.getContentAsString()); assertEquals("error content", this.servletResponse.getContentAsString());
assertEquals("someHeaderValue", this.servletResponse.getHeader("someHeader")); 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<Object> testException(Exception ex) { private ResponseEntity<Object> testException(Exception ex) {
ResponseEntity<Object> responseEntity = this.exceptionHandlerSupport.handleException(ex, this.request); try {
ResponseEntity<Object> 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 return responseEntity;
if (HttpStatus.INTERNAL_SERVER_ERROR.equals(responseEntity.getStatusCode())) {
assertSame(ex, this.servletRequest.getAttribute("javax.servlet.error.exception"));
} }
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 { private static class ApplicationExceptionHandler extends ResponseEntityExceptionHandler {
@Override @Override
protected ResponseEntity<Object> handleServletRequestBindingException(ServletRequestBindingException ex, protected ResponseEntity<Object> handleServletRequestBindingException(
HttpHeaders headers, HttpStatus status, WebRequest request) { ServletRequestBindingException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
headers.set("someHeader", "someHeaderValue"); headers.set("someHeader", "someHeaderValue");
return handleExceptionInternal(ex, "error content", headers, status, request); return handleExceptionInternal(ex, "error content", headers, status, request);

4
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 Note that Spring WebFlux does not have an equivalent for the Spring MVC
`ResponseEntityExceptionHandler` because WebFlux only raises `ResponseStatusException` `ResponseEntityExceptionHandler` because WebFlux only raises `ResponseStatusException`
(or sub-classes of), which and those do not need to be translated translation to an HTTP (or subclasses thereof), which and those do not need to be translated translation to
status code. an HTTP status code.
==== ====

2
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 body should consider extending
{api-spring-framework}/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandler.html[ResponseEntityExceptionHandler] {api-spring-framework}/web/servlet/mvc/method/annotation/ResponseEntityExceptionHandler.html[ResponseEntityExceptionHandler]
which provides handling for exceptions that Spring MVC raises along with hooks to 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 `ResponseEntityExceptionHandler`, annotate with `@ControllerAdvice`, override the
necessary methods, and declare it as a Spring bean. necessary methods, and declare it as a Spring bean.

Loading…
Cancel
Save