Add ExceptionHandlerSupport class

The new class is functionally equivalent to the
DefaultHandlerExceptionResolver (i.e. it translates Spring MVC
exceptions to various status codes) but uses an @ExceptionHandler
returning a ResponseEntity<Object>, which means it can be customized to
write error content to the body of the response.

Issue: SPR-9290
This commit is contained in:
Rossen Stoyanchev
2012-08-21 11:18:01 -04:00
parent 58daeea1e2
commit 1cf4a2facd
4 changed files with 747 additions and 70 deletions

View File

@@ -0,0 +1,397 @@
/*
* Copyright 2002-2012 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
*
* http://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.HashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.ConversionNotSupportedException;
import org.springframework.beans.TypeMismatchException;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.BindException;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
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.WebRequest;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException;
/**
* A convenient base for classes with {@link ExceptionHandler} methods providing
* infrastructure to handle standard Spring MVC exceptions. The functionality is
* equivalent to that of the {@link DefaultHandlerExceptionResolver} except it
* can be customized to write error content to the body of the response. If there
* is no need to write error content, use {@code DefaultHandlerExceptionResolver}
* instead.
*
* <p>It is expected the sub-classes will be annotated with
* {@link ControllerAdvice @ControllerAdvice} and that
* {@link ExceptionHandlerExceptionResolver} is configured to ensure this class
* applies to exceptions from any {@link RequestMapping @RequestMapping} method.
*
* @author Rossen Stoyanchev
* @since 3.2
*
* @see org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
*/
public abstract class ExceptionHandlerSupport {
protected final Log logger = LogFactory.getLog(getClass());
/**
* Log category to use when no mapped handler is found for a request.
* @see #pageNotFoundLogger
*/
public static final String PAGE_NOT_FOUND_LOG_CATEGORY = "org.springframework.web.servlet.PageNotFound";
/**
* Additional logger to use when no mapped handler is found for a request.
* @see #PAGE_NOT_FOUND_LOG_CATEGORY
*/
protected static final Log pageNotFoundLogger = LogFactory.getLog(PAGE_NOT_FOUND_LOG_CATEGORY);
/**
* Provides handling for standard Spring MVC exceptions.
* @param ex the target exception
* @param request the current request
*/
@ExceptionHandler(value={
NoSuchRequestHandlingMethodException.class,
HttpRequestMethodNotSupportedException.class,
HttpMediaTypeNotSupportedException.class,
HttpMediaTypeNotAcceptableException.class,
MissingServletRequestParameterException.class,
ServletRequestBindingException.class,
ConversionNotSupportedException.class,
TypeMismatchException.class,
HttpMessageNotReadableException.class,
HttpMessageNotWritableException.class,
MethodArgumentNotValidException.class,
MissingServletRequestPartException.class,
BindException.class
})
public final ResponseEntity<Object> handleException(Exception ex, WebRequest request) {
HttpHeaders headers = new HttpHeaders();
HttpStatus status;
Object body;
if (ex instanceof NoSuchRequestHandlingMethodException) {
status = HttpStatus.NOT_FOUND;
body = handleNoSuchRequestHandlingMethod((NoSuchRequestHandlingMethodException) ex, headers, status, request);
}
else if (ex instanceof HttpRequestMethodNotSupportedException) {
status = HttpStatus.METHOD_NOT_ALLOWED;
body = handleHttpRequestMethodNotSupported((HttpRequestMethodNotSupportedException) ex, headers, status, request);
}
else if (ex instanceof HttpMediaTypeNotSupportedException) {
status = HttpStatus.UNSUPPORTED_MEDIA_TYPE;
body = handleHttpMediaTypeNotSupported((HttpMediaTypeNotSupportedException) ex, headers, status, request);
}
else if (ex instanceof HttpMediaTypeNotAcceptableException) {
status = HttpStatus.NOT_ACCEPTABLE;
body = handleHttpMediaTypeNotAcceptable((HttpMediaTypeNotAcceptableException) ex, headers, status, request);
}
else if (ex instanceof MissingServletRequestParameterException) {
status = HttpStatus.BAD_REQUEST;
body = handleMissingServletRequestParameter((MissingServletRequestParameterException) ex, headers, status, request);
}
else if (ex instanceof ServletRequestBindingException) {
status = HttpStatus.BAD_REQUEST;
body = handleServletRequestBindingException((ServletRequestBindingException) ex, headers, status, request);
}
else if (ex instanceof ConversionNotSupportedException) {
status = HttpStatus.INTERNAL_SERVER_ERROR;
body = handleConversionNotSupported((ConversionNotSupportedException) ex, headers, status, request);
}
else if (ex instanceof TypeMismatchException) {
status = HttpStatus.BAD_REQUEST;
body = handleTypeMismatch((TypeMismatchException) ex, headers, status, request);
}
else if (ex instanceof HttpMessageNotReadableException) {
status = HttpStatus.BAD_REQUEST;
body = handleHttpMessageNotReadable((HttpMessageNotReadableException) ex, headers, status, request);
}
else if (ex instanceof HttpMessageNotWritableException) {
status = HttpStatus.INTERNAL_SERVER_ERROR;
body = handleHttpMessageNotWritable((HttpMessageNotWritableException) ex, headers, status, request);
}
else if (ex instanceof MethodArgumentNotValidException) {
status = HttpStatus.BAD_REQUEST;
body = handleMethodArgumentNotValid((MethodArgumentNotValidException) ex, headers, status, request);
}
else if (ex instanceof MissingServletRequestPartException) {
status = HttpStatus.BAD_REQUEST;
body = handleMissingServletRequestPart((MissingServletRequestPartException) ex, headers, status, request);
}
else if (ex instanceof BindException) {
status = HttpStatus.BAD_REQUEST;
body = handleBindException((BindException) ex, headers, status, request);
}
else {
logger.warn("Unknown exception type: " + ex.getClass().getName());
status = HttpStatus.INTERNAL_SERVER_ERROR;
body = handleExceptionInternal(ex, headers, status, request);
}
return new ResponseEntity<Object>(body, headers, status);
}
/**
* A single place to customize the response body of all Exception types.
* This method returns {@code null} by default.
* @param ex the exception
* @param headers the headers to be written to the response
* @param status the selected response status
* @param request the current request
*/
protected Object handleExceptionInternal(Exception ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
return null;
}
/**
* Customize the response for NoSuchRequestHandlingMethodException.
* This method logs a warning and delegates to
* {@link #handleExceptionInternal(Exception, HttpHeaders, HttpStatus, WebRequest)}.
* @param ex the exception
* @param headers the headers to be written to the response
* @param status the selected response status
* @param request the current request
* @return an Object or {@code null}
*/
protected Object handleNoSuchRequestHandlingMethod(NoSuchRequestHandlingMethodException ex,
HttpHeaders headers, HttpStatus status, WebRequest request) {
pageNotFoundLogger.warn(ex.getMessage());
return handleExceptionInternal(ex, headers, status, request);
}
/**
* Customize the response for HttpRequestMethodNotSupportedException.
* This method logs a warning, sets the "Allow" header, and delegates to
* {@link #handleExceptionInternal(Exception, HttpHeaders, HttpStatus, WebRequest)}.
* @param ex the exception
* @param headers the headers to be written to the response
* @param status the selected response status
* @param request the current request
* @return an Object or {@code null}
*/
protected Object handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException ex,
HttpHeaders headers, HttpStatus status, WebRequest request) {
pageNotFoundLogger.warn(ex.getMessage());
Set<HttpMethod> mediaTypes = new HashSet<HttpMethod>();
for (String value : ex.getSupportedMethods()) {
mediaTypes.add(HttpMethod.valueOf(value));
}
if (!mediaTypes.isEmpty()) {
headers.setAllow(mediaTypes);
}
return handleExceptionInternal(ex, headers, status, request);
}
/**
* Customize the response for HttpMediaTypeNotSupportedException.
* This method sets the "Accept" header and delegates to
* {@link #handleExceptionInternal(Exception, HttpHeaders, HttpStatus, WebRequest)}.
* @param ex the exception
* @param headers the headers to be written to the response
* @param status the selected response status
* @param request the current request
* @return an Object or {@code null}
*/
protected Object handleHttpMediaTypeNotSupported(HttpMediaTypeNotSupportedException ex,
HttpHeaders headers, HttpStatus status, WebRequest request) {
List<MediaType> mediaTypes = ex.getSupportedMediaTypes();
if (!CollectionUtils.isEmpty(mediaTypes)) {
headers.setAccept(mediaTypes);
}
return handleExceptionInternal(ex, headers, status, request);
}
/**
* Customize the response for HttpMediaTypeNotAcceptableException.
* This method delegates to {@link #handleExceptionInternal(Exception, HttpHeaders, HttpStatus, WebRequest)}.
* @param ex the exception
* @param headers the headers to be written to the response
* @param status the selected response status
* @param request the current request
* @return an Object or {@code null}
*/
protected Object handleHttpMediaTypeNotAcceptable(HttpMediaTypeNotAcceptableException ex,
HttpHeaders headers, HttpStatus status, WebRequest request) {
return handleExceptionInternal(ex, headers, status, request);
}
/**
* Customize the response for MissingServletRequestParameterException.
* This method delegates to {@link #handleExceptionInternal(Exception, HttpHeaders, HttpStatus, WebRequest)}.
* @param ex the exception
* @param headers the headers to be written to the response
* @param status the selected response status
* @param request the current request
* @return an Object or {@code null}
*/
protected Object handleMissingServletRequestParameter(MissingServletRequestParameterException ex,
HttpHeaders headers, HttpStatus status, WebRequest request) {
return handleExceptionInternal(ex, headers, status, request);
}
/**
* Customize the response for ServletRequestBindingException.
* This method delegates to {@link #handleExceptionInternal(Exception, HttpHeaders, HttpStatus, WebRequest)}.
* @param ex the exception
* @param headers the headers to be written to the response
* @param status the selected response status
* @param request the current request
* @return an Object or {@code null}
*/
protected Object handleServletRequestBindingException(ServletRequestBindingException ex,
HttpHeaders headers, HttpStatus status, WebRequest request) {
return handleExceptionInternal(ex, headers, status, request);
}
/**
* Customize the response for ConversionNotSupportedException.
* This method delegates to {@link #handleExceptionInternal(Exception, HttpHeaders, HttpStatus, WebRequest)}.
* @param ex the exception
* @param headers the headers to be written to the response
* @param status the selected response status
* @param request the current request
* @return an Object or {@code null}
*/
protected Object handleConversionNotSupported(ConversionNotSupportedException ex,
HttpHeaders headers, HttpStatus status, WebRequest request) {
return handleExceptionInternal(ex, headers, status, request);
}
/**
* Customize the response for TypeMismatchException.
* This method delegates to {@link #handleExceptionInternal(Exception, HttpHeaders, HttpStatus, WebRequest)}.
* @param ex the exception
* @param headers the headers to be written to the response
* @param status the selected response status
* @param request the current request
* @return an Object or {@code null}
*/
protected Object handleTypeMismatch(TypeMismatchException ex, HttpHeaders headers,
HttpStatus status, WebRequest request) {
return handleExceptionInternal(ex, headers, status, request);
}
/**
* Customize the response for HttpMessageNotReadableException.
* This method delegates to {@link #handleExceptionInternal(Exception, HttpHeaders, HttpStatus, WebRequest)}.
* @param ex the exception
* @param headers the headers to be written to the response
* @param status the selected response status
* @param request the current request
* @return an Object or {@code null}
*/
protected Object handleHttpMessageNotReadable(HttpMessageNotReadableException ex,
HttpHeaders headers, HttpStatus status, WebRequest request) {
return handleExceptionInternal(ex, headers, status, request);
}
/**
* Customize the response for HttpMessageNotWritableException.
* This method delegates to {@link #handleExceptionInternal(Exception, HttpHeaders, HttpStatus, WebRequest)}.
* @param ex the exception
* @param headers the headers to be written to the response
* @param status the selected response status
* @param request the current request
* @return an Object or {@code null}
*/
protected Object handleHttpMessageNotWritable(HttpMessageNotWritableException ex,
HttpHeaders headers, HttpStatus status, WebRequest request) {
return handleExceptionInternal(ex, headers, status, request);
}
/**
* Customize the response for MethodArgumentNotValidException.
* This method delegates to {@link #handleExceptionInternal(Exception, HttpHeaders, HttpStatus, WebRequest)}.
* @param ex the exception
* @param headers the headers to be written to the response
* @param status the selected response status
* @param request the current request
* @return an Object or {@code null}
*/
protected Object handleMethodArgumentNotValid(MethodArgumentNotValidException ex,
HttpHeaders headers, HttpStatus status, WebRequest request) {
return handleExceptionInternal(ex, headers, status, request);
}
/**
* Customize the response for MissingServletRequestPartException.
* This method delegates to {@link #handleExceptionInternal(Exception, HttpHeaders, HttpStatus, WebRequest)}.
* @param ex the exception
* @param headers the headers to be written to the response
* @param status the selected response status
* @param request the current request
* @return an Object or {@code null}
*/
protected Object handleMissingServletRequestPart(MissingServletRequestPartException ex,
HttpHeaders headers, HttpStatus status, WebRequest request) {
return handleExceptionInternal(ex, headers, status, request);
}
/**
* Customize the response for BindException.
* This method delegates to {@link #handleExceptionInternal(Exception, HttpHeaders, HttpStatus, WebRequest)}.
* @param ex the exception
* @param headers the headers to be written to the response
* @param status the selected response status
* @param request the current request
* @return an Object or {@code null}
*/
protected Object handleBindException(BindException ex, HttpHeaders headers,
HttpStatus status, WebRequest request) {
return handleExceptionInternal(ex, headers, status, request);
}
}

View File

@@ -59,6 +59,8 @@ import org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMeth
* @author Arjen Poutsma
* @author Rossen Stoyanchev
* @since 3.0
*
* @see org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerSupport
* @see #handleNoSuchRequestHandlingMethod
* @see #handleHttpRequestMethodNotSupported
* @see #handleHttpMediaTypeNotSupported