001/* 002 * Copyright 2002-2019 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.web.servlet.mvc.method.annotation; 018 019import java.util.List; 020import java.util.Set; 021 022import javax.servlet.http.HttpServletResponse; 023 024import org.apache.commons.logging.Log; 025import org.apache.commons.logging.LogFactory; 026 027import org.springframework.beans.ConversionNotSupportedException; 028import org.springframework.beans.TypeMismatchException; 029import org.springframework.http.HttpHeaders; 030import org.springframework.http.HttpMethod; 031import org.springframework.http.HttpStatus; 032import org.springframework.http.MediaType; 033import org.springframework.http.ResponseEntity; 034import org.springframework.http.converter.HttpMessageConverter; 035import org.springframework.http.converter.HttpMessageNotReadableException; 036import org.springframework.http.converter.HttpMessageNotWritableException; 037import org.springframework.lang.Nullable; 038import org.springframework.util.CollectionUtils; 039import org.springframework.validation.BindException; 040import org.springframework.web.HttpMediaTypeNotAcceptableException; 041import org.springframework.web.HttpMediaTypeNotSupportedException; 042import org.springframework.web.HttpRequestMethodNotSupportedException; 043import org.springframework.web.bind.MethodArgumentNotValidException; 044import org.springframework.web.bind.MissingPathVariableException; 045import org.springframework.web.bind.MissingServletRequestParameterException; 046import org.springframework.web.bind.ServletRequestBindingException; 047import org.springframework.web.bind.annotation.ControllerAdvice; 048import org.springframework.web.bind.annotation.ExceptionHandler; 049import org.springframework.web.context.request.ServletWebRequest; 050import org.springframework.web.context.request.WebRequest; 051import org.springframework.web.context.request.async.AsyncRequestTimeoutException; 052import org.springframework.web.multipart.support.MissingServletRequestPartException; 053import org.springframework.web.servlet.NoHandlerFoundException; 054import org.springframework.web.util.WebUtils; 055 056/** 057 * A convenient base class for {@link ControllerAdvice @ControllerAdvice} classes 058 * that wish to provide centralized exception handling across all 059 * {@code @RequestMapping} methods through {@code @ExceptionHandler} methods. 060 * 061 * <p>This base class provides an {@code @ExceptionHandler} method for handling 062 * internal Spring MVC exceptions. This method returns a {@code ResponseEntity} 063 * for writing to the response with a {@link HttpMessageConverter message converter}, 064 * in contrast to 065 * {@link org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver 066 * DefaultHandlerExceptionResolver} which returns a 067 * {@link org.springframework.web.servlet.ModelAndView ModelAndView}. 068 * 069 * <p>If there is no need to write error content to the response body, or when 070 * using view resolution (e.g., via {@code ContentNegotiatingViewResolver}), 071 * then {@code DefaultHandlerExceptionResolver} is good enough. 072 * 073 * <p>Note that in order for an {@code @ControllerAdvice} subclass to be 074 * detected, {@link ExceptionHandlerExceptionResolver} must be configured. 075 * 076 * @author Rossen Stoyanchev 077 * @since 3.2 078 * @see #handleException(Exception, WebRequest) 079 * @see org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver 080 */ 081public abstract class ResponseEntityExceptionHandler { 082 083 /** 084 * Log category to use when no mapped handler is found for a request. 085 * @see #pageNotFoundLogger 086 */ 087 public static final String PAGE_NOT_FOUND_LOG_CATEGORY = "org.springframework.web.servlet.PageNotFound"; 088 089 /** 090 * Specific logger to use when no mapped handler is found for a request. 091 * @see #PAGE_NOT_FOUND_LOG_CATEGORY 092 */ 093 protected static final Log pageNotFoundLogger = LogFactory.getLog(PAGE_NOT_FOUND_LOG_CATEGORY); 094 095 /** 096 * Common logger for use in subclasses. 097 */ 098 protected final Log logger = LogFactory.getLog(getClass()); 099 100 101 /** 102 * Provides handling for standard Spring MVC exceptions. 103 * @param ex the target exception 104 * @param request the current request 105 */ 106 @ExceptionHandler({ 107 HttpRequestMethodNotSupportedException.class, 108 HttpMediaTypeNotSupportedException.class, 109 HttpMediaTypeNotAcceptableException.class, 110 MissingPathVariableException.class, 111 MissingServletRequestParameterException.class, 112 ServletRequestBindingException.class, 113 ConversionNotSupportedException.class, 114 TypeMismatchException.class, 115 HttpMessageNotReadableException.class, 116 HttpMessageNotWritableException.class, 117 MethodArgumentNotValidException.class, 118 MissingServletRequestPartException.class, 119 BindException.class, 120 NoHandlerFoundException.class, 121 AsyncRequestTimeoutException.class 122 }) 123 @Nullable 124 public final ResponseEntity<Object> handleException(Exception ex, WebRequest request) throws Exception { 125 HttpHeaders headers = new HttpHeaders(); 126 127 if (ex instanceof HttpRequestMethodNotSupportedException) { 128 HttpStatus status = HttpStatus.METHOD_NOT_ALLOWED; 129 return handleHttpRequestMethodNotSupported((HttpRequestMethodNotSupportedException) ex, headers, status, request); 130 } 131 else if (ex instanceof HttpMediaTypeNotSupportedException) { 132 HttpStatus status = HttpStatus.UNSUPPORTED_MEDIA_TYPE; 133 return handleHttpMediaTypeNotSupported((HttpMediaTypeNotSupportedException) ex, headers, status, request); 134 } 135 else if (ex instanceof HttpMediaTypeNotAcceptableException) { 136 HttpStatus status = HttpStatus.NOT_ACCEPTABLE; 137 return handleHttpMediaTypeNotAcceptable((HttpMediaTypeNotAcceptableException) ex, headers, status, request); 138 } 139 else if (ex instanceof MissingPathVariableException) { 140 HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR; 141 return handleMissingPathVariable((MissingPathVariableException) ex, headers, status, request); 142 } 143 else if (ex instanceof MissingServletRequestParameterException) { 144 HttpStatus status = HttpStatus.BAD_REQUEST; 145 return handleMissingServletRequestParameter((MissingServletRequestParameterException) ex, headers, status, request); 146 } 147 else if (ex instanceof ServletRequestBindingException) { 148 HttpStatus status = HttpStatus.BAD_REQUEST; 149 return handleServletRequestBindingException((ServletRequestBindingException) ex, headers, status, request); 150 } 151 else if (ex instanceof ConversionNotSupportedException) { 152 HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR; 153 return handleConversionNotSupported((ConversionNotSupportedException) ex, headers, status, request); 154 } 155 else if (ex instanceof TypeMismatchException) { 156 HttpStatus status = HttpStatus.BAD_REQUEST; 157 return handleTypeMismatch((TypeMismatchException) ex, headers, status, request); 158 } 159 else if (ex instanceof HttpMessageNotReadableException) { 160 HttpStatus status = HttpStatus.BAD_REQUEST; 161 return handleHttpMessageNotReadable((HttpMessageNotReadableException) ex, headers, status, request); 162 } 163 else if (ex instanceof HttpMessageNotWritableException) { 164 HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR; 165 return handleHttpMessageNotWritable((HttpMessageNotWritableException) ex, headers, status, request); 166 } 167 else if (ex instanceof MethodArgumentNotValidException) { 168 HttpStatus status = HttpStatus.BAD_REQUEST; 169 return handleMethodArgumentNotValid((MethodArgumentNotValidException) ex, headers, status, request); 170 } 171 else if (ex instanceof MissingServletRequestPartException) { 172 HttpStatus status = HttpStatus.BAD_REQUEST; 173 return handleMissingServletRequestPart((MissingServletRequestPartException) ex, headers, status, request); 174 } 175 else if (ex instanceof BindException) { 176 HttpStatus status = HttpStatus.BAD_REQUEST; 177 return handleBindException((BindException) ex, headers, status, request); 178 } 179 else if (ex instanceof NoHandlerFoundException) { 180 HttpStatus status = HttpStatus.NOT_FOUND; 181 return handleNoHandlerFoundException((NoHandlerFoundException) ex, headers, status, request); 182 } 183 else if (ex instanceof AsyncRequestTimeoutException) { 184 HttpStatus status = HttpStatus.SERVICE_UNAVAILABLE; 185 return handleAsyncRequestTimeoutException((AsyncRequestTimeoutException) ex, headers, status, request); 186 } 187 else { 188 // Unknown exception, typically a wrapper with a common MVC exception as cause 189 // (since @ExceptionHandler type declarations also match first-level causes): 190 // We only deal with top-level MVC exceptions here, so let's rethrow the given 191 // exception for further processing through the HandlerExceptionResolver chain. 192 throw ex; 193 } 194 } 195 196 /** 197 * Customize the response for HttpRequestMethodNotSupportedException. 198 * <p>This method logs a warning, sets the "Allow" header, and delegates to 199 * {@link #handleExceptionInternal}. 200 * @param ex the exception 201 * @param headers the headers to be written to the response 202 * @param status the selected response status 203 * @param request the current request 204 * @return a {@code ResponseEntity} instance 205 */ 206 protected ResponseEntity<Object> handleHttpRequestMethodNotSupported( 207 HttpRequestMethodNotSupportedException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { 208 209 pageNotFoundLogger.warn(ex.getMessage()); 210 211 Set<HttpMethod> supportedMethods = ex.getSupportedHttpMethods(); 212 if (!CollectionUtils.isEmpty(supportedMethods)) { 213 headers.setAllow(supportedMethods); 214 } 215 return handleExceptionInternal(ex, null, headers, status, request); 216 } 217 218 /** 219 * Customize the response for HttpMediaTypeNotSupportedException. 220 * <p>This method sets the "Accept" header and delegates to 221 * {@link #handleExceptionInternal}. 222 * @param ex the exception 223 * @param headers the headers to be written to the response 224 * @param status the selected response status 225 * @param request the current request 226 * @return a {@code ResponseEntity} instance 227 */ 228 protected ResponseEntity<Object> handleHttpMediaTypeNotSupported( 229 HttpMediaTypeNotSupportedException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { 230 231 List<MediaType> mediaTypes = ex.getSupportedMediaTypes(); 232 if (!CollectionUtils.isEmpty(mediaTypes)) { 233 headers.setAccept(mediaTypes); 234 } 235 236 return handleExceptionInternal(ex, null, headers, status, request); 237 } 238 239 /** 240 * Customize the response for HttpMediaTypeNotAcceptableException. 241 * <p>This method delegates to {@link #handleExceptionInternal}. 242 * @param ex the exception 243 * @param headers the headers to be written to the response 244 * @param status the selected response status 245 * @param request the current request 246 * @return a {@code ResponseEntity} instance 247 */ 248 protected ResponseEntity<Object> handleHttpMediaTypeNotAcceptable( 249 HttpMediaTypeNotAcceptableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { 250 251 return handleExceptionInternal(ex, null, headers, status, request); 252 } 253 254 /** 255 * Customize the response for MissingPathVariableException. 256 * <p>This method delegates to {@link #handleExceptionInternal}. 257 * @param ex the exception 258 * @param headers the headers to be written to the response 259 * @param status the selected response status 260 * @param request the current request 261 * @return a {@code ResponseEntity} instance 262 * @since 4.2 263 */ 264 protected ResponseEntity<Object> handleMissingPathVariable( 265 MissingPathVariableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { 266 267 return handleExceptionInternal(ex, null, headers, status, request); 268 } 269 270 /** 271 * Customize the response for MissingServletRequestParameterException. 272 * <p>This method delegates to {@link #handleExceptionInternal}. 273 * @param ex the exception 274 * @param headers the headers to be written to the response 275 * @param status the selected response status 276 * @param request the current request 277 * @return a {@code ResponseEntity} instance 278 */ 279 protected ResponseEntity<Object> handleMissingServletRequestParameter( 280 MissingServletRequestParameterException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { 281 282 return handleExceptionInternal(ex, null, headers, status, request); 283 } 284 285 /** 286 * Customize the response for ServletRequestBindingException. 287 * <p>This method delegates to {@link #handleExceptionInternal}. 288 * @param ex the exception 289 * @param headers the headers to be written to the response 290 * @param status the selected response status 291 * @param request the current request 292 * @return a {@code ResponseEntity} instance 293 */ 294 protected ResponseEntity<Object> handleServletRequestBindingException( 295 ServletRequestBindingException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { 296 297 return handleExceptionInternal(ex, null, headers, status, request); 298 } 299 300 /** 301 * Customize the response for ConversionNotSupportedException. 302 * <p>This method delegates to {@link #handleExceptionInternal}. 303 * @param ex the exception 304 * @param headers the headers to be written to the response 305 * @param status the selected response status 306 * @param request the current request 307 * @return a {@code ResponseEntity} instance 308 */ 309 protected ResponseEntity<Object> handleConversionNotSupported( 310 ConversionNotSupportedException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { 311 312 return handleExceptionInternal(ex, null, headers, status, request); 313 } 314 315 /** 316 * Customize the response for TypeMismatchException. 317 * <p>This method delegates to {@link #handleExceptionInternal}. 318 * @param ex the exception 319 * @param headers the headers to be written to the response 320 * @param status the selected response status 321 * @param request the current request 322 * @return a {@code ResponseEntity} instance 323 */ 324 protected ResponseEntity<Object> handleTypeMismatch( 325 TypeMismatchException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { 326 327 return handleExceptionInternal(ex, null, headers, status, request); 328 } 329 330 /** 331 * Customize the response for HttpMessageNotReadableException. 332 * <p>This method delegates to {@link #handleExceptionInternal}. 333 * @param ex the exception 334 * @param headers the headers to be written to the response 335 * @param status the selected response status 336 * @param request the current request 337 * @return a {@code ResponseEntity} instance 338 */ 339 protected ResponseEntity<Object> handleHttpMessageNotReadable( 340 HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { 341 342 return handleExceptionInternal(ex, null, headers, status, request); 343 } 344 345 /** 346 * Customize the response for HttpMessageNotWritableException. 347 * <p>This method delegates to {@link #handleExceptionInternal}. 348 * @param ex the exception 349 * @param headers the headers to be written to the response 350 * @param status the selected response status 351 * @param request the current request 352 * @return a {@code ResponseEntity} instance 353 */ 354 protected ResponseEntity<Object> handleHttpMessageNotWritable( 355 HttpMessageNotWritableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { 356 357 return handleExceptionInternal(ex, null, headers, status, request); 358 } 359 360 /** 361 * Customize the response for MethodArgumentNotValidException. 362 * <p>This method delegates to {@link #handleExceptionInternal}. 363 * @param ex the exception 364 * @param headers the headers to be written to the response 365 * @param status the selected response status 366 * @param request the current request 367 * @return a {@code ResponseEntity} instance 368 */ 369 protected ResponseEntity<Object> handleMethodArgumentNotValid( 370 MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { 371 372 return handleExceptionInternal(ex, null, headers, status, request); 373 } 374 375 /** 376 * Customize the response for MissingServletRequestPartException. 377 * <p>This method delegates to {@link #handleExceptionInternal}. 378 * @param ex the exception 379 * @param headers the headers to be written to the response 380 * @param status the selected response status 381 * @param request the current request 382 * @return a {@code ResponseEntity} instance 383 */ 384 protected ResponseEntity<Object> handleMissingServletRequestPart( 385 MissingServletRequestPartException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { 386 387 return handleExceptionInternal(ex, null, headers, status, request); 388 } 389 390 /** 391 * Customize the response for BindException. 392 * <p>This method delegates to {@link #handleExceptionInternal}. 393 * @param ex the exception 394 * @param headers the headers to be written to the response 395 * @param status the selected response status 396 * @param request the current request 397 * @return a {@code ResponseEntity} instance 398 */ 399 protected ResponseEntity<Object> handleBindException( 400 BindException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { 401 402 return handleExceptionInternal(ex, null, headers, status, request); 403 } 404 405 /** 406 * Customize the response for NoHandlerFoundException. 407 * <p>This method delegates to {@link #handleExceptionInternal}. 408 * @param ex the exception 409 * @param headers the headers to be written to the response 410 * @param status the selected response status 411 * @param request the current request 412 * @return a {@code ResponseEntity} instance 413 * @since 4.0 414 */ 415 protected ResponseEntity<Object> handleNoHandlerFoundException( 416 NoHandlerFoundException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { 417 418 return handleExceptionInternal(ex, null, headers, status, request); 419 } 420 421 /** 422 * Customize the response for AsyncRequestTimeoutException. 423 * <p>This method delegates to {@link #handleExceptionInternal}. 424 * @param ex the exception 425 * @param headers the headers to be written to the response 426 * @param status the selected response status 427 * @param webRequest the current request 428 * @return a {@code ResponseEntity} instance 429 * @since 4.2.8 430 */ 431 @Nullable 432 protected ResponseEntity<Object> handleAsyncRequestTimeoutException( 433 AsyncRequestTimeoutException ex, HttpHeaders headers, HttpStatus status, WebRequest webRequest) { 434 435 if (webRequest instanceof ServletWebRequest) { 436 ServletWebRequest servletWebRequest = (ServletWebRequest) webRequest; 437 HttpServletResponse response = servletWebRequest.getResponse(); 438 if (response != null && response.isCommitted()) { 439 if (logger.isWarnEnabled()) { 440 logger.warn("Async request timed out"); 441 } 442 return null; 443 } 444 } 445 446 return handleExceptionInternal(ex, null, headers, status, webRequest); 447 } 448 449 /** 450 * A single place to customize the response body of all exception types. 451 * <p>The default implementation sets the {@link WebUtils#ERROR_EXCEPTION_ATTRIBUTE} 452 * request attribute and creates a {@link ResponseEntity} from the given 453 * body, headers, and status. 454 * @param ex the exception 455 * @param body the body for the response 456 * @param headers the headers for the response 457 * @param status the response status 458 * @param request the current request 459 */ 460 protected ResponseEntity<Object> handleExceptionInternal( 461 Exception ex, @Nullable Object body, HttpHeaders headers, HttpStatus status, WebRequest request) { 462 463 if (HttpStatus.INTERNAL_SERVER_ERROR.equals(status)) { 464 request.setAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE, ex, WebRequest.SCOPE_REQUEST); 465 } 466 return new ResponseEntity<>(body, headers, status); 467 } 468 469}