Java中的异常处理,常用的处理方式,直接throw 一个异常对象,或者捕获catch一个异常,让程序继续执行,但是在一个项目中,这种简单处理异常的方式并不可取,因为在业务代码中直接抛出或者捕获异常,跟业务的耦合性都太高,而且代码冗余不利于维护;更好的处理方式,应该是利用切面编程的思想,自定义一个全局异常类,返回一个统一规范的异常信息;
首先我们先理解一下Java中的异常体系;
如上图所示,Java中的异常的 主要分为Error和Exception两大类,都继承自Throwable,
1) Exception
是程序正常运行中,可以预料的意外情况,可以被捕获,进行相应的处理;Exception又分为checked Exception和unchecked Exception,可检查的异常在源代码里必须显示地进行捕获处理,这是编译期检查的一部分;如IOException;不检查的异常就是运行时异常,类似于NullPointException,ArrayIndexOutOfBoundsException
2) Error
Error是指在正常情况下,不大可能出现的情况,绝大部分的Error都会导致程序(如JVM)处于非正常的,不可恢复状态,如OutOfMemoryError之类的,都是Error的子类
@ControllerAdvice是在类上声明的注解,与其他注解组合,实现不同的定制功能,其用法主要有三点:
自定义一个全局异常处理类,该类中并使用@ControllerAdvice修饰
使用@ExceptionHandler注解捕获指定或自定义的异常;
使用@ControllerAdvice集成@ExceptionHandler的方法到一个类中;
必须定义一个通用的异常捕获方法,便于捕获未定义的异常信息;
自定一个异常类,捕获针对项目或业务的异常;
异常的对象信息补充到统一结果枚举中;
1) 通用异常结果集的封装
package com.zach.exception.common.result;
/**
* @Classname ResponResult
* @Description:
* @Date 2020/5/2 17:23
* @Created by Zach
*/
public interface ResponseResult {
}
import java.util.Date;
/**
* @Classname DefalultErrorResult
* @Description:
* @Date 2020/5/2 17:24
* @Created by Zach
*/
public class DefaultErrorResult implements ResponseResult {
/**
* HTTP响应状态码 {@link HttpStatus}
*/
private Integer status;
/**
* HTTP响应状态码的英文提示
*/
private String error;
/**
* 异常堆栈的精简信息
*
*/
private String message;
/**
* 我们系统内部自定义的返回值编码,{@link ResultCode} 它是对错误更加详细的编码
*
* 备注:spring boot默认返回异常时,该字段为null
*/
private Integer code;
/**
* 调用接口路径
*/
private String path;
/**
* 异常的名字
*/
private String exception;
/**
* 异常的错误传递的数据
*/
private Object errors;
/**
* 时间戳
*/
private Date timestamp;
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public String getError() {
return error;
}
public void setError(String error) {
this.error = error;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public String getException() {
return exception;
}
public void setException(String exception) {
this.exception = exception;
}
public Object getErrors() {
return errors;
}
public void setErrors(Object errors) {
this.errors = errors;
}
public Date getTimestamp() {
return timestamp;
}
public void setTimestamp(Date timestamp) {
this.timestamp = timestamp;
}
public static DefaultErrorResult failure(ResultCode resultCode, Throwable e, HttpStatus httpStatus, Object errors) {
DefaultErrorResult result = DefaultErrorResult.failure(resultCode, e, httpStatus);
result.setErrors(errors);
return result;
}
public static DefaultErrorResult failure(ResultCode resultCode, Throwable e, HttpStatus httpStatus) {
DefaultErrorResult result = new DefaultErrorResult();
result.setCode(resultCode.getCode());
result.setMessage(resultCode.getMessage());
result.setStatus(httpStatus.value());
result.setError(httpStatus.getReasonPhrase());
result.setException(e.getClass().getName());
String path = RequestContextUtils.getRequest().getRequestURI();
result.setPath(path);
result.setTimestamp(new Date());
return result;
}
public static DefaultErrorResult failure(BusinessException e) {
BusinessExceptionEnum ee = BusinessExceptionEnum.getByEClass(e.getClass());
if (ee != null) {
return DefaultErrorResult.failure(ee.getResultCode(), e, ee.getHttpStatus(), e.getData());
}
DefaultErrorResult defaultErrorResult = DefaultErrorResult.failure(e.getResultCode() == null ? ResultCode.SUCCESS : e.getResultCode(), e, HttpStatus.OK, e.getData());
if (!StringUtils.isEmpty(e.getMessage())) {
defaultErrorResult.setMessage(e.getMessage());
}
return defaultErrorResult;
}
}
/**
* @Classname CommonResult
* @Description:
* @Date 2020/5/2 17:35
* @Created by Zach
*/
public class CommonResult implements ResponseResult {
private Integer code;
private String msg;
private T data;
public CommonResult() {
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public CommonResult(Integer code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
}
public static CommonResult success() {
CommonResult result = new CommonResult();
result.setResultCode(ResultCode.SUCCESS);
return result;
}
public static CommonResult success(T data) {
CommonResult result = new CommonResult();
result.setResultCode(ResultCode.SUCCESS);
result.setData(data);
return result;
}
public static CommonResult failure(ResultCode resultCode) {
CommonResult result = new CommonResult();
result.setResultCode(resultCode);
return result;
}
public static CommonResult failure(ResultCode resultCode, T data) {
CommonResult result = new CommonResult();
result.setResultCode(resultCode);
result.setData(data);
return result;
}
public static CommonResult failure(String message) {
CommonResult result = new CommonResult();
result.setCode(ResultCode.PARAM_IS_INVALID.getCode());
result.setMsg(message);
return result;
}
private void setResultCode(ResultCode code) {
this.code = code.getCode();
this.msg = code.getMessage();
}
}
2) 全局异常抽象处理类
/**
* @Classname BaseGlobExceptionAdvice
* @Description: 全局异常抽象类
* @Date 2020/5/2 17:01
* @Created by Zach
*/
public abstract class BaseGlobalExceptionAdvice {
protected final Logger logger = LoggerFactory.getLogger(getClass());
/**
* 违反约束异常
*/
protected DefaultErrorResult handleConstraintViolationException(ConstraintViolationException e, HttpServletRequest request) {
logger.info("handleConstraintViolationException start, uri:{}, caused by: ", request.getRequestURI(), e);
List parameterInvalidItemList = ParameterInvalidItemHelper.convertCVSetToParameterInvalidItemList(e.getConstraintViolations());
return DefaultErrorResult.failure(ResultCode.PARAM_IS_INVALID, e, HttpStatus.BAD_REQUEST, parameterInvalidItemList);
}
/**
* 处理验证参数封装错误时异常
*/
protected DefaultErrorResult handleConstraintViolationException(HttpMessageNotReadableException e, HttpServletRequest request) {
logger.info("handleConstraintViolationException start, uri:{}, caused by: ", request.getRequestURI(), e);
return DefaultErrorResult.failure(ResultCode.PARAM_IS_INVALID, e, HttpStatus.BAD_REQUEST);
}
/**
* 处理参数绑定时异常(反400错误码)
*/
protected DefaultErrorResult handleBindException(BindException e, HttpServletRequest request) {
logger.info("handleBindException start, uri:{}, caused by: ", request.getRequestURI(), e);
List parameterInvalidItemList = ParameterInvalidItemHelper.convertBindingResultToMapParameterInvalidItemList(e.getBindingResult());
return DefaultErrorResult.failure(ResultCode.PARAM_IS_INVALID, e, HttpStatus.BAD_REQUEST, parameterInvalidItemList);
}
/**
* 处理使用@Validated注解时,参数验证错误异常(反400错误码)
*/
protected DefaultErrorResult handleMethodArgumentNotValidException(MethodArgumentNotValidException e, HttpServletRequest request) {
logger.info("handleMethodArgumentNotValidException start, uri:{}, caused by: ", request.getRequestURI(), e);
List parameterInvalidItemList = ParameterInvalidItemHelper.convertBindingResultToMapParameterInvalidItemList(e.getBindingResult());
return DefaultErrorResult.failure(ResultCode.PARAM_IS_INVALID, e, HttpStatus.BAD_REQUEST, parameterInvalidItemList);
}
/**
* 处理使用@Validated注解时,接口请求类型不支持(反400错误码)
*/
protected DefaultErrorResult handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e, HttpServletRequest request) {
logger.info("handleHttpRequestMethodNotSupportedException start, uri:{}, caused by: ", request.getRequestURI(), e);
return DefaultErrorResult.failure(ResultCode.INTERFACE_METHOD_NOT_SUPPORT, e, HttpStatus.BAD_REQUEST);
}
/**
* 处理通用自定义业务异常
*/
protected ResponseEntity handleBusinessException(BusinessException e, HttpServletRequest request) {
logger.info("handleBusinessException start, uri:{}, exception:{}, caused by: {}", request.getRequestURI(), e.getClass(), e.getMessage());
DefaultErrorResult defaultErrorResult = DefaultErrorResult.failure(e);
return ResponseEntity
.status(HttpStatus.valueOf(defaultErrorResult.getStatus()))
.body(defaultErrorResult);
}
/**
* 处理运行时系统异常(反500错误码)
*/
protected DefaultErrorResult handleRuntimeException(RuntimeException e, HttpServletRequest request) {
logger.error("handleRuntimeException start, uri:{}, caused by: ", request.getRequestURI(), e);
return DefaultErrorResult.failure(ResultCode.SYSTEM_INNER_ERROR, e, HttpStatus.INTERNAL_SERVER_ERROR);
}
/**
* 处理系统异常(反500错误码)
*/
protected DefaultErrorResult handleException(Exception e, HttpServletRequest request) {
logger.error("handleRuntimeException start, uri:{}, caused by: ", request.getRequestURI(), e);
return DefaultErrorResult.failure(ResultCode.SYSTEM_INNER_ERROR, e, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
3) 具体实现
/**
* @Classname GlobalExceptionAdviceAdvice
* @Description:
* @Date 2020/5/2 17:44
* @Created by Zach
*/
@RestControllerAdvice
public class GlobalExceptionAdviceAdvice extends BaseGlobalExceptionAdvice {
/**
* @Description TODO
* @date 2020/5/2 17:48
* @auther Zach
*/
@Override
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(ConstraintViolationException.class)
public DefaultErrorResult handleConstraintViolationException(ConstraintViolationException e, HttpServletRequest request) {
return super.handleConstraintViolationException(e, request);
}
@Override
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(HttpMessageNotReadableException.class)
public DefaultErrorResult handleConstraintViolationException(HttpMessageNotReadableException e, HttpServletRequest request) {
return super.handleConstraintViolationException(e, request);
}
@Override
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(BindException.class)
public DefaultErrorResult handleBindException(BindException e, HttpServletRequest request) {
return super.handleBindException(e, request);
}
@Override
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MethodArgumentNotValidException.class)
public DefaultErrorResult handleMethodArgumentNotValidException(MethodArgumentNotValidException e, HttpServletRequest request) {
return super.handleMethodArgumentNotValidException(e, request);
}
@Override
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public DefaultErrorResult handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e, HttpServletRequest request) {
return super.handleHttpRequestMethodNotSupportedException(e, request);
}
/**
* 处理自定义异常
*/
@Override
@ExceptionHandler(BusinessException.class)
public ResponseEntity handleBusinessException(BusinessException e, HttpServletRequest request) {
return super.handleBusinessException(e, request);
}
/**
* 处理运行时异常
*/
@Override
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler(RuntimeException.class)
public DefaultErrorResult handleRuntimeException(RuntimeException e, HttpServletRequest request) {
return super.handleRuntimeException(e, request);
}
/**
* 处理异常
*/
@Override
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler(Exception.class)
public DefaultErrorResult handleException(Exception e, HttpServletRequest request) {
return super.handleException(e, request);
}
}
4) 统一结果集的返回
/**
* @Classname ResponseResultAdvice
* @Description:
* @Date 2020/5/2 18:09
* @Created by Zach
*/
@ControllerAdvice
public class ResponseResultAdvice implements ResponseBodyAdvice {
@Override
public boolean supports(MethodParameter methodParameter, Class aClass) {
//通过自定义注解IgnoreResponseAdvice的方式,来决定是否需要统一的结果集处理,留一个注解开关,更加灵活变通
if (methodParameter.getDeclaringClass().isAnnotationPresent(IgnoreResponseAdvice.class)) {
return false;
}
if (methodParameter.getMethod().isAnnotationPresent(IgnoreResponseAdvice.class)) {
return false;
}
return true;
}
@Override
public Object beforeBodyWrite(Object object, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
if (object instanceof CommonResult)
return object;
if (object instanceof DefaultErrorResult) {
DefaultErrorResult defaultErrorResult = DefaultErrorResult.class.cast(object);
return new CommonResult(defaultErrorResult.getCode(),defaultErrorResult.getMessage(),defaultErrorResult.getErrors());
}
return CommonResult.success(object);
}
}
以上就是主要的实现过程,自定义了生产环境中各种可能出现的异常情况,详细代码请移至: https://github.com/Zach-Zhang/exception-customize