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:
parent
58daeea1e2
commit
1cf4a2facd
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -0,0 +1,233 @@
|
|||
/*
|
||||
* 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 static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
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.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
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.context.request.ServletWebRequest;
|
||||
import org.springframework.web.context.request.WebRequest;
|
||||
import org.springframework.web.context.support.StaticWebApplicationContext;
|
||||
import org.springframework.web.multipart.support.MissingServletRequestPartException;
|
||||
import org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException;
|
||||
import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver;
|
||||
|
||||
/**
|
||||
* Test fixture for {@link ExceptionHandlerSupport}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public class ExceptionHandlerSupportTests {
|
||||
|
||||
private ExceptionHandlerSupport exceptionHandlerSupport;
|
||||
|
||||
private DefaultHandlerExceptionResolver defaultExceptionResolver;
|
||||
|
||||
private WebRequest request;
|
||||
|
||||
private HttpServletRequest servletRequest;
|
||||
|
||||
private MockHttpServletResponse servletResponse;
|
||||
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
this.servletRequest = new MockHttpServletRequest();
|
||||
this.servletResponse = new MockHttpServletResponse();
|
||||
this.request = new ServletWebRequest(this.servletRequest, this.servletResponse);
|
||||
|
||||
this.exceptionHandlerSupport = new ApplicationExceptionHandler();
|
||||
this.defaultExceptionResolver = new DefaultHandlerExceptionResolver();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void supportsAllDefaultHandlerExceptionResolverExceptionTypes() throws Exception {
|
||||
|
||||
Method annotMethod = ExceptionHandlerSupport.class.getMethod("handleException", Exception.class, WebRequest.class);
|
||||
ExceptionHandler annot = annotMethod.getAnnotation(ExceptionHandler.class);
|
||||
List<Class<? extends Throwable>> supportedTypes = Arrays.asList(annot.value());
|
||||
|
||||
for (Method method : DefaultHandlerExceptionResolver.class.getDeclaredMethods()) {
|
||||
Class<?>[] paramTypes = method.getParameterTypes();
|
||||
if (method.getName().startsWith("handle") && (paramTypes.length == 4)) {
|
||||
String name = paramTypes[0].getSimpleName();
|
||||
assertTrue("@ExceptionHandler is missing " + name, supportedTypes.contains(paramTypes[0]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noSuchRequestHandlingMethod() {
|
||||
Exception ex = new NoSuchRequestHandlingMethodException("GET", TestController.class);
|
||||
testException(ex);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void httpRequestMethodNotSupported() {
|
||||
List<String> supported = Arrays.asList("POST", "DELETE");
|
||||
Exception ex = new HttpRequestMethodNotSupportedException("GET", supported);
|
||||
|
||||
ResponseEntity<Object> responseEntity = testException(ex);
|
||||
assertEquals(EnumSet.of(HttpMethod.POST, HttpMethod.DELETE), responseEntity.getHeaders().getAllow());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleHttpMediaTypeNotSupported() {
|
||||
List<MediaType> acceptable = Arrays.asList(MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_XML);
|
||||
Exception ex = new HttpMediaTypeNotSupportedException(MediaType.APPLICATION_JSON, acceptable);
|
||||
|
||||
ResponseEntity<Object> responseEntity = testException(ex);
|
||||
assertEquals(acceptable, responseEntity.getHeaders().getAccept());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void httpMediaTypeNotAcceptable() {
|
||||
Exception ex = new HttpMediaTypeNotAcceptableException("");
|
||||
testException(ex);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void missingServletRequestParameter() {
|
||||
Exception ex = new MissingServletRequestParameterException("param", "type");
|
||||
testException(ex);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void servletRequestBindingException() {
|
||||
Exception ex = new ServletRequestBindingException("message");
|
||||
testException(ex);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void conversionNotSupported() {
|
||||
Exception ex = new ConversionNotSupportedException(new Object(), Object.class, null);
|
||||
testException(ex);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void typeMismatch() {
|
||||
Exception ex = new TypeMismatchException("foo", String.class);
|
||||
testException(ex);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void httpMessageNotReadable() {
|
||||
Exception ex = new HttpMessageNotReadableException("message");
|
||||
testException(ex);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void httpMessageNotWritable() {
|
||||
Exception ex = new HttpMessageNotWritableException("");
|
||||
testException(ex);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void methodArgumentNotValid() {
|
||||
Exception ex = new MethodArgumentNotValidException(null, null);
|
||||
testException(ex);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void missingServletRequestPart() {
|
||||
Exception ex = new MissingServletRequestPartException("partName");
|
||||
testException(ex);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bindException() {
|
||||
Exception ex = new BindException(new Object(), "name");
|
||||
testException(ex);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void controllerAdvice() throws Exception {
|
||||
StaticWebApplicationContext cxt = new StaticWebApplicationContext();
|
||||
cxt.registerSingleton("exceptionHandler", ApplicationExceptionHandler.class);
|
||||
cxt.refresh();
|
||||
|
||||
ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver();
|
||||
resolver.setApplicationContext(cxt);
|
||||
resolver.afterPropertiesSet();
|
||||
|
||||
ServletRequestBindingException ex = new ServletRequestBindingException("message");
|
||||
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"));
|
||||
}
|
||||
|
||||
|
||||
private ResponseEntity<Object> testException(Exception ex) {
|
||||
ResponseEntity<Object> responseEntity = this.exceptionHandlerSupport.handleException(ex, this.request);
|
||||
this.defaultExceptionResolver.resolveException(this.servletRequest, this.servletResponse, null, ex);
|
||||
|
||||
assertEquals(this.servletResponse.getStatus(), responseEntity.getStatusCode().value());
|
||||
|
||||
return responseEntity;
|
||||
}
|
||||
|
||||
|
||||
private static class TestController {
|
||||
}
|
||||
|
||||
@ControllerAdvice
|
||||
private static class ApplicationExceptionHandler extends ExceptionHandlerSupport {
|
||||
|
||||
@Override
|
||||
protected Object handleServletRequestBindingException(ServletRequestBindingException ex,
|
||||
HttpHeaders headers, HttpStatus status, WebRequest request) {
|
||||
|
||||
headers.set("someHeader", "someHeaderValue");
|
||||
return "error content";
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -3646,12 +3646,20 @@ public String onSubmit(<emphasis role="bold">@RequestPart("meta-data") MetaData
|
|||
is only a matter of implementing the
|
||||
<literal>resolveException(Exception, Handler)</literal> method and
|
||||
returning a <classname>ModelAndView</classname>, you may also use the provided
|
||||
<classname>SimpleMappingExceptionResolver</classname>. This resolver
|
||||
<classname>SimpleMappingExceptionResolver</classname> or create
|
||||
<interfacename>@ExceptionHandler</interfacename> methods.
|
||||
The <classname>SimpleMappingExceptionResolver</classname>
|
||||
enables you to take the class name of any exception that might be thrown
|
||||
and map it to a view name. This is functionally equivalent to the
|
||||
exception mapping feature from the Servlet API, but it is also possible
|
||||
to implement more finely grained mappings of exceptions from different
|
||||
handlers.</para>
|
||||
handlers. The <interfacename>@ExceptionHandler</interfacename> annotation on
|
||||
the other hand can be used on methods that should be invoked to handle an
|
||||
exception. Such methods may be defined locally within an
|
||||
<interfacename>@Controller</interfacename> or may apply globally to all
|
||||
<interfacename>@RequestMapping</interfacename> methods when defined within
|
||||
an <interfacename>@ControllerAdvice</interfacename> class.
|
||||
The following sections explain this in more detail.</para>
|
||||
</section>
|
||||
|
||||
<section id="mvc-ann-exceptionhandler">
|
||||
|
@ -3659,36 +3667,44 @@ public String onSubmit(<emphasis role="bold">@RequestPart("meta-data") MetaData
|
|||
|
||||
<para>The <interfacename>HandlerExceptionResolver</interfacename> interface
|
||||
and the <classname>SimpleMappingExceptionResolver</classname> implementations
|
||||
allow you to map Exceptions to specific views along with some Java logic
|
||||
before forwarding to those views. However, in some cases, especially when
|
||||
working with programmatic clients (Ajax or non-browser) it is more
|
||||
convenient to set the status and optionally write error information to the
|
||||
response body.</para>
|
||||
allow you to map Exceptions to specific views declaratively along with some
|
||||
optional Java logic before forwarding to those views. However, in some cases,
|
||||
especially when relying on <interfacename>@ResponseBody</interfacename> methods
|
||||
rather than on view resolution, it may be more convenient to directly set the
|
||||
status of the response and optionally write error content to the body of the
|
||||
response.</para>
|
||||
|
||||
<para>For that you can use <interfacename>@ExceptionHandler</interfacename>
|
||||
methods. When present within a controller such methods apply to exceptions
|
||||
raised by that contoroller or any of its sub-classes.
|
||||
Or you can also declare <interfacename>@ExceptionHandler</interfacename>
|
||||
methods in an <interfacename>@ControllerAdvice</interfacename>-annotated
|
||||
class and such methods apply to any controller.
|
||||
<para>You can do that with <interfacename>@ExceptionHandler</interfacename>
|
||||
methods. When declared within a controller such methods apply to exceptions
|
||||
raised by <interfacename>@RequestMapping</interfacename> methods of that
|
||||
contoroller (or any of its sub-classes). You can also declare an
|
||||
<interfacename>@ExceptionHandler</interfacename> method within an
|
||||
<interfacename>@ControllerAdvice</interfacename> class in which case it
|
||||
handles exceptions from <interfacename>@RequestMapping</interfacename>
|
||||
methods from any controller.
|
||||
The <interfacename>@ControllerAdvice</interfacename> annotation is
|
||||
a component annotation allowing implementation classes to be autodetected
|
||||
through classpath scanning.
|
||||
</para>
|
||||
|
||||
<para>Here is an example with a controller-level
|
||||
a component annotation, which can be used with classpath scanning. It is
|
||||
automatically enabled when using the MVC namespace and Java config, or
|
||||
otherwise depending on whether the
|
||||
<classname>ExceptionHandlerExceptionResolver</classname> is configured or not.
|
||||
Below is an example of a controller-local
|
||||
<interfacename>@ExceptionHandler</interfacename> method:</para>
|
||||
|
||||
<programlisting language="java">@Controller
|
||||
public class SimpleController {
|
||||
|
||||
// other controller method omitted
|
||||
|
||||
// @RequestMapping methods omitted ...
|
||||
|
||||
|
||||
@ExceptionHandler(IOException.class)
|
||||
public ResponseEntity handleIOException(IOException ex) {
|
||||
public ResponseEntity<String> handleIOException(IOException ex) {
|
||||
|
||||
// prepare responseEntity
|
||||
|
||||
return responseEntity;
|
||||
}
|
||||
|
||||
}</programlisting>
|
||||
|
||||
<para>The <classname>@ExceptionHandler</classname> value can be set to
|
||||
|
@ -3711,30 +3727,25 @@ public class SimpleController {
|
|||
<interfacename>@ResponseBody</interfacename> to have the method return value
|
||||
converted with message converters and written to the response stream.</para>
|
||||
|
||||
<note><para>To better understand how <interfacename>@ExceptionHandler</interfacename>
|
||||
methods work, consider that in Spring MVC there is only one abstraction
|
||||
for handling exceptions and that's the
|
||||
<interfacename>HandlerExceptionResolver</interfacename>. There is a special
|
||||
implementation of that interface,
|
||||
the <classname>ExceptionHandlerExceptionResolver</classname>, which detects
|
||||
and invokes <interfacename>@ExceptionHandler</interfacename> methods.</para></note>
|
||||
</section>
|
||||
|
||||
<section id="mvc-ann-rest-spring-mvc-exceptions">
|
||||
<title>Handling of Spring MVC Exceptions</title>
|
||||
<title>Handling Standard Spring MVC Exceptions</title>
|
||||
|
||||
<para>Spring MVC may raise a number of exceptions while processing a request.
|
||||
A <classname>SimpleMappingExceptionResolver</classname> can be used to easily
|
||||
map any exception to a default error view or to more specific error views if
|
||||
desired. However when responding to programmatic clients you may prefer to
|
||||
translate specific exceptions to the appropriate status that indicates a
|
||||
client error (4xx) or a server error (5xx).</para>
|
||||
<para>Spring MVC may raise a number of exceptions while processing
|
||||
a request. The <classname>SimpleMappingExceptionResolver</classname> can easily
|
||||
map any exception to a default error view as needed.
|
||||
However, when working with clients that interpret responses in an automated
|
||||
way you will want to set specific status code on the response. Depending on
|
||||
the exception raised the status code may indicate a client error (4xx) or a
|
||||
server error (5xx).</para>
|
||||
|
||||
<para>For this reason Spring MVC provides the
|
||||
<classname>DefaultHandlerExceptionResolver</classname>, which translates specific
|
||||
Spring MVC exceptions by setting a specific response status code. By default,
|
||||
this resolver is registered by the <classname>DispatcherServlet</classname>.
|
||||
The following table describes some of the exceptions it handles:
|
||||
<para>The <classname>DefaultHandlerExceptionResolver</classname> translates
|
||||
Spring MVC exceptions to specific error status codes. It is registered
|
||||
by default with the MVC namespace, the MVC Java config. and also by the
|
||||
the <classname>DispatcherServlet</classname> (i.e. when not using the MVC
|
||||
namespace or Java config). Listed below are some of the exceptions handled
|
||||
by this resolver and the corresponding status codes:
|
||||
<informaltable>
|
||||
<tgroup cols="2">
|
||||
<thead>
|
||||
|
@ -3822,38 +3833,19 @@ public class SimpleController {
|
|||
</informaltable>
|
||||
</para>
|
||||
|
||||
<note><para>If you explicitly register one or more
|
||||
<interfacename>HandlerExceptionResolver</interfacename> instances in your configuration
|
||||
then the defaults registered by the <classname>DispatcherServlet</classname> are
|
||||
cancelled. This is standard behavior with regards to
|
||||
<classname>DispatcherServlet</classname> defaults.
|
||||
See <xref linkend="mvc-servlet-special-bean-types"/> for more details.</para></note>
|
||||
<para>The <classname>DefaultHandlerExceptionResolver</classname> works
|
||||
transparently by setting the status of the response. However, it stops short
|
||||
of writing any error content to the body of the response while your
|
||||
application may need to add developer-friendly content to every error
|
||||
response for example when providing a REST API.</para>
|
||||
|
||||
<para>If building a REST API, then it's very likely you will want to
|
||||
write some additional information about the error to the body of the response
|
||||
consistent with the API's error handling throughout. This includes the handling of
|
||||
Spring MVC exceptions, for which the <classname>DefaultHandlerExceptionResolver</classname>
|
||||
only sets the status code and doesn't assume how or what content should be written
|
||||
to the body.</para>
|
||||
|
||||
<para>Instead you can create an <interfacename>@ControllerAdvice</interfacename>
|
||||
class that handles each of the exceptions handled by the
|
||||
<classname>DefaultHandlerExceptionResolver</classname> while also writing
|
||||
developer-friendly API error information to the response body consistent with
|
||||
the rest of all API error handling of the application. For example:</para>
|
||||
|
||||
<programlisting language="java">@ControllerAdvice
|
||||
public class ApplicationExceptionResolver {
|
||||
|
||||
@ExceptionHandler
|
||||
public ResponseEntity handleMediaTypeNotAcceptable(HttpMediaTypeNotAcceptableException ex) {
|
||||
MyApiError error = ... ;
|
||||
return new ResponseEntity(error, HttpStatus.SC_NOT_ACCEPTABLE);
|
||||
}
|
||||
|
||||
// more @ExceptionHandler methods ...
|
||||
|
||||
}</programlisting>
|
||||
<para>To achieve this extend <classname>ExceptionHandlerSupport</classname>,
|
||||
a convenient base class with an <interfacename>@ExceptionHandler</interfacename>
|
||||
method that handles standard Spring MVC exceptions just as the
|
||||
<classname>DefaultHandlerExceptionResolver</classname> does but also
|
||||
allowing you to prepare error content for the body of the response.
|
||||
See the Javadoc of <classname>ExceptionHandlerSupport</classname>
|
||||
for more details.</para>
|
||||
</section>
|
||||
|
||||
<section id="mvc-ann-annotated-exceptions">
|
||||
|
@ -3869,6 +3861,59 @@ public class ApplicationExceptionResolver {
|
|||
|
||||
</section>
|
||||
|
||||
<section id="mvc-ann-customer-servlet-container-error-page">
|
||||
<title>Customizing the Default Servlet Container Error Page</title>
|
||||
|
||||
<para>When the status of the response is set to an error status code
|
||||
and the body of the response is empty, Servlet containers commonly render
|
||||
an HTML formatted error page.
|
||||
To customize the default error page of the container, you can
|
||||
declare an <code><error-page></code> element in
|
||||
<filename>web.xml</filename>. Up until Servlet 3, that element had to
|
||||
be mapped to a specific status code or exception type. Starting with
|
||||
Servlet 3 an error page does not need to be mapped, which effectively
|
||||
means the specified location customizes the default Servlet container
|
||||
error page.</para>
|
||||
|
||||
<programlisting language="xml"><error-page>
|
||||
<location>/error</location>
|
||||
</error-page>
|
||||
</programlisting>
|
||||
|
||||
<para>Note that the actual location for the error page can be a
|
||||
JSP page or some other URL within the container including one handled
|
||||
through an <interfacename>@Controller</interfacename> method:</para>
|
||||
|
||||
<para>When writing error information, the status code and the error message
|
||||
set on the <interfacename>HttpServletResponse</interfacename> can be
|
||||
accessed through request attributes in a controller:</para>
|
||||
|
||||
<programlisting language="java">@Controller
|
||||
public class ErrorController {
|
||||
|
||||
@RequestMapping(value="/error", produces="application/json")
|
||||
@ResponseBody
|
||||
public Map<String, Object> handle(HttpServletRequest request) {
|
||||
|
||||
Map<String, Object> map = new HashMap<String, Object>();
|
||||
map.put("status", request.getAttribute("javax.servlet.error.status_code"));
|
||||
map.put("reason", request.getAttribute("javax.servlet.error.message"));
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
}</programlisting>
|
||||
|
||||
<para>or in a JSP:</para>
|
||||
|
||||
<programlisting language="xml"><%@ page contentType="application/json" pageEncoding="UTF-8"%>
|
||||
{
|
||||
status:<%=request.getAttribute("javax.servlet.error.status_code") %>,
|
||||
reason:<%=request.getAttribute("javax.servlet.error.message") %>
|
||||
}</programlisting>
|
||||
|
||||
</section>
|
||||
|
||||
</section>
|
||||
|
||||
<section id="mvc-coc">
|
||||
|
|
Loading…
Reference in New Issue