对于一些我们自定义的一些异常,或者一些其他的异常信心,我们不希望页面看到一大串bug信息,想友好的提示给用户,这样的需要可以利用的spring的全局异常处理机制实现,spring提供了多种异常处理的方式,下面这个方式只是其中一个,我们项目中是这么用的,参考一下我们项目中的实现,做一次记录。
先认识一个类ResponseEntityExceptionHandler
这个类里面封装了各种可能出现的异常,ResponseEntityExceptionHandler源码如下:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.springframework.web.servlet.mvc.method.annotation;
import java.util.List;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
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.MissingPathVariableException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.ServletRequestBindingException;
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.request.async.AsyncRequestTimeoutException;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException;
public abstract class ResponseEntityExceptionHandler {
public static final String PAGE_NOT_FOUND_LOG_CATEGORY = "org.springframework.web.servlet.PageNotFound";
protected static final Log pageNotFoundLogger = LogFactory.getLog("org.springframework.web.servlet.PageNotFound");
protected final Log logger = LogFactory.getLog(this.getClass());
public ResponseEntityExceptionHandler() {
}
@ExceptionHandler({NoSuchRequestHandlingMethodException.class, HttpRequestMethodNotSupportedException.class, HttpMediaTypeNotSupportedException.class, HttpMediaTypeNotAcceptableException.class, MissingPathVariableException.class, MissingServletRequestParameterException.class, ServletRequestBindingException.class, ConversionNotSupportedException.class, TypeMismatchException.class, HttpMessageNotReadableException.class, HttpMessageNotWritableException.class, MethodArgumentNotValidException.class, MissingServletRequestPartException.class, BindException.class, NoHandlerFoundException.class, AsyncRequestTimeoutException.class})
public final ResponseEntity<Object> handleException(Exception ex, WebRequest request) {
HttpHeaders headers = new HttpHeaders();
HttpStatus status;
if (ex instanceof NoSuchRequestHandlingMethodException) {
status = HttpStatus.NOT_FOUND;
return this.handleNoSuchRequestHandlingMethod((NoSuchRequestHandlingMethodException)ex, headers, status, request);
} else if (ex instanceof HttpRequestMethodNotSupportedException) {
status = HttpStatus.METHOD_NOT_ALLOWED;
return this.handleHttpRequestMethodNotSupported((HttpRequestMethodNotSupportedException)ex, headers, status, request);
} else if (ex instanceof HttpMediaTypeNotSupportedException) {
status = HttpStatus.UNSUPPORTED_MEDIA_TYPE;
return this.handleHttpMediaTypeNotSupported((HttpMediaTypeNotSupportedException)ex, headers, status, request);
} else if (ex instanceof HttpMediaTypeNotAcceptableException) {
status = HttpStatus.NOT_ACCEPTABLE;
return this.handleHttpMediaTypeNotAcceptable((HttpMediaTypeNotAcceptableException)ex, headers, status, request);
} else if (ex instanceof MissingPathVariableException) {
status = HttpStatus.INTERNAL_SERVER_ERROR;
return this.handleMissingPathVariable((MissingPathVariableException)ex, headers, status, request);
} else if (ex instanceof MissingServletRequestParameterException) {
status = HttpStatus.BAD_REQUEST;
return this.handleMissingServletRequestParameter((MissingServletRequestParameterException)ex, headers, status, request);
} else if (ex instanceof ServletRequestBindingException) {
status = HttpStatus.BAD_REQUEST;
return this.handleServletRequestBindingException((ServletRequestBindingException)ex, headers, status, request);
} else if (ex instanceof ConversionNotSupportedException) {
status = HttpStatus.INTERNAL_SERVER_ERROR;
return this.handleConversionNotSupported((ConversionNotSupportedException)ex, headers, status, request);
} else if (ex instanceof TypeMismatchException) {
status = HttpStatus.BAD_REQUEST;
return this.handleTypeMismatch((TypeMismatchException)ex, headers, status, request);
} else if (ex instanceof HttpMessageNotReadableException) {
status = HttpStatus.BAD_REQUEST;
return this.handleHttpMessageNotReadable((HttpMessageNotReadableException)ex, headers, status, request);
} else if (ex instanceof HttpMessageNotWritableException) {
status = HttpStatus.INTERNAL_SERVER_ERROR;
return this.handleHttpMessageNotWritable((HttpMessageNotWritableException)ex, headers, status, request);
} else if (ex instanceof MethodArgumentNotValidException) {
status = HttpStatus.BAD_REQUEST;
return this.handleMethodArgumentNotValid((MethodArgumentNotValidException)ex, headers, status, request);
} else if (ex instanceof MissingServletRequestPartException) {
status = HttpStatus.BAD_REQUEST;
return this.handleMissingServletRequestPart((MissingServletRequestPartException)ex, headers, status, request);
} else if (ex instanceof BindException) {
status = HttpStatus.BAD_REQUEST;
return this.handleBindException((BindException)ex, headers, status, request);
} else if (ex instanceof NoHandlerFoundException) {
status = HttpStatus.NOT_FOUND;
return this.handleNoHandlerFoundException((NoHandlerFoundException)ex, headers, status, request);
} else if (ex instanceof AsyncRequestTimeoutException) {
status = HttpStatus.SERVICE_UNAVAILABLE;
return this.handleAsyncRequestTimeoutException((AsyncRequestTimeoutException)ex, headers, status, request);
} else {
if (this.logger.isWarnEnabled()) {
this.logger.warn("Unknown exception type: " + ex.getClass().getName());
}
status = HttpStatus.INTERNAL_SERVER_ERROR;
return this.handleExceptionInternal(ex, (Object)null, headers, status, request);
}
}
protected ResponseEntity<Object> handleExceptionInternal(Exception ex, Object body, HttpHeaders headers, HttpStatus status, WebRequest request) {
if (HttpStatus.INTERNAL_SERVER_ERROR.equals(status)) {
request.setAttribute("javax.servlet.error.exception", ex, 0);
}
return new ResponseEntity(body, headers, status);
}
/** @deprecated */
@Deprecated
protected ResponseEntity<Object> handleNoSuchRequestHandlingMethod(NoSuchRequestHandlingMethodException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
pageNotFoundLogger.warn(ex.getMessage());
return this.handleExceptionInternal(ex, (Object)null, headers, status, request);
}
protected ResponseEntity<Object> handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
pageNotFoundLogger.warn(ex.getMessage());
Set<HttpMethod> supportedMethods = ex.getSupportedHttpMethods();
if (!CollectionUtils.isEmpty(supportedMethods)) {
headers.setAllow(supportedMethods);
}
return this.handleExceptionInternal(ex, (Object)null, headers, status, request);
}
protected ResponseEntity<Object> handleHttpMediaTypeNotSupported(HttpMediaTypeNotSupportedException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
List<MediaType> mediaTypes = ex.getSupportedMediaTypes();
if (!CollectionUtils.isEmpty(mediaTypes)) {
headers.setAccept(mediaTypes);
}
return this.handleExceptionInternal(ex, (Object)null, headers, status, request);
}
protected ResponseEntity<Object> handleHttpMediaTypeNotAcceptable(HttpMediaTypeNotAcceptableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
return this.handleExceptionInternal(ex, (Object)null, headers, status, request);
}
protected ResponseEntity<Object> handleMissingPathVariable(MissingPathVariableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
return this.handleExceptionInternal(ex, (Object)null, headers, status, request);
}
protected ResponseEntity<Object> handleMissingServletRequestParameter(MissingServletRequestParameterException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
return this.handleExceptionInternal(ex, (Object)null, headers, status, request);
}
protected ResponseEntity<Object> handleServletRequestBindingException(ServletRequestBindingException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
return this.handleExceptionInternal(ex, (Object)null, headers, status, request);
}
protected ResponseEntity<Object> handleConversionNotSupported(ConversionNotSupportedException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
return this.handleExceptionInternal(ex, (Object)null, headers, status, request);
}
protected ResponseEntity<Object> handleTypeMismatch(TypeMismatchException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
return this.handleExceptionInternal(ex, (Object)null, headers, status, request);
}
protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
return this.handleExceptionInternal(ex, (Object)null, headers, status, request);
}
protected ResponseEntity<Object> handleHttpMessageNotWritable(HttpMessageNotWritableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
return this.handleExceptionInternal(ex, (Object)null, headers, status, request);
}
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
return this.handleExceptionInternal(ex, (Object)null, headers, status, request);
}
protected ResponseEntity<Object> handleMissingServletRequestPart(MissingServletRequestPartException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
return this.handleExceptionInternal(ex, (Object)null, headers, status, request);
}
protected ResponseEntity<Object> handleBindException(BindException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
return this.handleExceptionInternal(ex, (Object)null, headers, status, request);
}
protected ResponseEntity<Object> handleNoHandlerFoundException(NoHandlerFoundException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
return this.handleExceptionInternal(ex, (Object)null, headers, status, request);
}
protected ResponseEntity<Object> handleAsyncRequestTimeoutException(AsyncRequestTimeoutException ex, HttpHeaders headers, HttpStatus status, WebRequest webRequest) {
if (webRequest instanceof ServletWebRequest) {
ServletWebRequest servletRequest = (ServletWebRequest)webRequest;
HttpServletRequest request = (HttpServletRequest)servletRequest.getNativeRequest(HttpServletRequest.class);
HttpServletResponse response = (HttpServletResponse)servletRequest.getNativeResponse(HttpServletResponse.class);
if (response.isCommitted()) {
if (this.logger.isErrorEnabled()) {
this.logger.error("Async timeout for " + request.getMethod() + " [" + request.getRequestURI() + "]");
}
return null;
}
}
return this.handleExceptionInternal(ex, (Object)null, headers, status, webRequest);
}
}
处理结果都是封装成一个ResponseEntity对象。通过ResponseEntity我们可以指定需要响应的状态码、header和body等信息,响应的body会被HttpMessageConverter处理,所以如果你响应的body是一个对象,而你的HttpMessageConverter列表中有一个是可以把对象转换为JSON的HttpMessageConverter,那么客户端收到的就是一段JSON。ResponseEntityExceptionHandler是一个抽象类,通常我们需要定义一个用来处理异常的使用@ControllerAdvice
注解标注的异常处理类来继承自ResponseEntityExceptionHandler。
写一个类使用@ControllerAdvice注解标记,
继承ResponseEntityExceptionHandler
package com.soecode.lyf.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import javax.servlet.http.HttpServletRequest;
/**
* @Description
* @Author DJZ-WWS
* @Date 2019/6/11 20:00
*/
@ControllerAdvice
public class BaseGlobalExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler(value = Exception.class)
@ResponseBody
public ResponseResult<Object> exceptionHandler(HttpServletRequest req, Exception e) {
if (EmptyUtils.isNotEmpty(e.getMessage())&&e.getMessage().indexOf("提示") != -1) {
return ResponseResult.fail(HttpStatus.INTERNAL_SERVER_ERROR.value(), e.getMessage());
}
return ResponseResult.fail(HttpStatus.INTERNAL_SERVER_ERROR.value(), "服务内部错误");
}
@ExceptionHandler(value = ParamInvalidException.class)
@ResponseBody
public ResponseResult<Object> paramInvalidExceptionHandler(HttpServletRequest req, ParamInvalidException e) throws Exception {
return ResponseResult.fail(HttpStatus.BAD_REQUEST.value(), e.getMessage());
}
}
返回的结果会被解析成json格式,这个类是我们项目中的,这里直接拿来用了。
package com.soecode.lyf.exception;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
@ApiModel(value = "ResponseResult", discriminator = "ͨ通用返回信息", subTypes = {ResponseResult.class})
public class ResponseResult<T> {
@ApiModelProperty("状态码")
private int code;
@ApiModelProperty("返回描述")
private String msg;
@ApiModelProperty("返回对象")
private T result;
private static final Integer HTTP_OK = 200;
private static final Integer HTTP_INTERNAL_SERVER_ERROR = 500;
private static final Integer HTTP_REQUEST_TIMEOUT = 408;
public static <T> ResponseResult<T> success() {
return new ResponseResultBuilder<T>().code(HTTP_OK).msg("success").build();
}
public static <T> ResponseResult<T> success(T result) {
return new ResponseResultBuilder<T>().code(HTTP_OK).msg("success").result(result).build();
}
public static <T> ResponseResult<T> success(T result, String msg) {
return new ResponseResultBuilder<T>().code(HTTP_OK).msg(msg).result(result).build();
}
public static <T> ResponseResult<T> fail() {
return new ResponseResultBuilder<T>().code(HTTP_INTERNAL_SERVER_ERROR).build();
}
public static <T> ResponseResult<T> fail(Exception e) {
return new ResponseResultBuilder<T>().code(HTTP_INTERNAL_SERVER_ERROR).msg(e.getMessage().toString()).build();
}
public static <T> ResponseResult<T> fail(String msg) {
return new ResponseResultBuilder<T>().code(HTTP_INTERNAL_SERVER_ERROR).msg(msg).build();
}
public static <T> ResponseResult<T> fail(Integer code, String msg) {
return new ResponseResultBuilder<T>().code(code).msg(msg).build();
}
public static String buildSuccessResultStr(Object result) {
if (null == result) {
return "{\"msg\": \"success\",\"code\": " + HTTP_OK + ",\"result\": " + result + "}";
}
return "{\"msg\": \"success\",\"code\": " + HTTP_OK + ",\"result\": \"" + result + "\"}";
}
}
下面的代码是一个自定义异常,这里只是写了两个异常处理,一个是默认的Exception异常,一个是自定义的异常,我们可以将我们自定义的异常放在这个类里,根据异常的不同可以设置返回不同的状态码。
package com.soecode.lyf.exception;
public class ParamInvalidException extends Exception {
public ParamInvalidException() {
}
public ParamInvalidException(String message) {
super(message);
}
public ParamInvalidException(String message, Throwable cause) {
super(message, cause);
}
public ParamInvalidException(Throwable cause) {
super(cause);
}
public ParamInvalidException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
在业务中的应用
@RequestMapping(value = "/list", method = RequestMethod.GET)
@ApiOperation(notes = "获取所有的书", value = "获取所有的书", produces = MediaType.APPLICATION_JSON_VALUE)
private List<Book> list() throws ParamInvalidException {
if(1==1){
throw new ParamInvalidException("异常处理成功");
}
List<Book> list = bookService.getList();
return list;
}
使用swagger的测试结果如下:
代码改成如下配置,模拟一空指针,程序肯定会出现空指针
测试结果
对于其他的几种异常处理方式可以参见:
https://www.jianshu.com/p/f968b8dcf95a