跳到主要内容

异常处理

Java中的异常处理是开发过程中很重要的一个环节,如果异常处理不好可能会出现无法很快找到 BUG、系统中明明有问题却没有任何异常日志等问题。本文中列出一些异常处理规范,供大家参考。

异常分类

在Java中异常分为 Exception 以及 Error 两种类型,而 Exception 又可分为 Checked ExceptionUnchecked Exception

Error 无需处理。发生 Error 一般都是比较严重的问题,代码层次也处理不了。

Unchecked Exception

对于 Unchecked Exception 也是不处理的,即不用捕获,直接让其抛到顶层,最终交给 Spring 的 ControllerAdvice 中的 ExceptionHandler 来处理。

这类异常通常不捕获代码也是不会报错。而这类异常通常继承自 RuntimeException。或者说只要是继承自 RuntimeException 都是 Unchecked Exception

Checked Exception

Checked Exception 是在 Java 中的一种异常类型,它是一种编译时异常(checked exception)。在 Java 中,编译时异常是指在编译阶段必须进行处理的异常,即在代码中必须显式地处理这些异常,否则编译器会报错。

Checked Exception 通常是由外部因素导致的异常,例如文件不存在、网络连接中断等,这些异常是程序无法控制的,但是程序需要在编译时就做出相应的处理以避免程序在运行时出错。

如果调用的方法中抛出了异常(Checked Exception),一般情况下需要先捕获,然后包装成自定义的业务异常(如:ServiceException)并抛出。如下:

try {
File file = new File("122");
file.createNewFile();
} catch (IOException e) {
throw new ServiceException("XXX_0001", "无法创建文件", e);
}
java
注意

在新抛出的异常中一定要包含原异常栈。而在后续的异常拦截中也需要将异常栈打印输出,否则会出现具体异常无法找到的问题。

throw new ServiceException("XXX_0001", "无法创建文件"); 这样写是不允许的。

在后续处理异常时,要打印完整的异常栈信息。

@ExceptionHandler(value = ServiceException.class)
public ResponseBean<String> serviceException(ServiceException serviceException) {
ResponseBean<String> result = new ResponseBean<>();
result.setErrorCode(serviceException.getErrorCode());
result.setMessage(serviceException.getMessage());
result.setStatus(serviceException.getStatus());
log.error(serviceException.getErrorCode() + "-" + serviceException.getMessage(), serviceException);
return result;
}
java

业务异常

在系统中一般需要定义一个通用的业务异常,通用业务异常应当定义为 Unchecked Exception,即需要继承自 RuntimeException。一般通用业务异常中需要包含错误代码,如下:

通用业务异常定义
public class ServiceException extends RuntimeException {
/**
* 错误编码
*/
@Getter
private final String errorCode;

public ServiceException(String message) {
super(message);
this.errorCode = CommonError.SERVICE_ERROR.getErrorCode();
}

public ServiceException(String errorCode, String message) {
super(message);
this.errorCode = errorCode;
}

public ServiceException(String errorCode, String message, Throwable cause) {
super(message, cause);
this.errorCode = errorCode;
}

public ServiceException(ErrorInfo errorInfo) {
super(errorInfo.getErrorMessage());
this.errorCode = errorInfo.getErrorCode();
}

public ServiceException(ErrorInfo errorInfo, Throwable cause) {
super(errorInfo.getErrorMessage(), cause);
this.errorCode = errorInfo.getErrorCode();
}
}
java

对于系统中的业务逻辑校验,如必填字段校验不通过、数据重复提交等。则应当已抛出异常的方式来处理。如:添加权限时,首先判断权限编码是否已存在,如果存在抛出异常。

private void checkPermissionCode(String permissionCode, String excludeId) {
Integer count = this.sysPermissionMapper.countByPermissionCode(permissionCode, excludeId);
if (count != null && count > 0) {
throw new ServiceException(PermissionError.PERMISSION_CODE_EXIST);
}
}
java

除使用通用的业务异常外,也可以自定义业务异常,自定义的业务异常需要继承自 ServiceException

一些需要单独处理的 Checked Exception

下面总结了一些需要在 Spring 的 ControllerAdvice 中单独处理的 Checked Exception。

  • org.springframework.web.HttpRequestMethodNotSupportedException:请求接口的方法与接口暴露的方法不一致时,Spring 会抛出此异常。
  • org.springframework.web.bind.MethodArgumentNotValidException:参数校验不通过时抛出此异常,当使用 @NotNull@Valid 等方式进行请求参数验证时,如若验证不通过会抛出此异常,可以从此异常信息中获取具体的错误信息。
  • org.springframework.web.bind.MissingServletRequestParameterException:必填参数缺失时会抛出此异常,当 GET 请求中的参数被标记为 @RequestParam(require = true) 时,如果前端调用接口时未传此参数,会抛出此异常。
  • org.springframework.web.multipart.MaxUploadSizeExceededException:当上传的文件超过限制时抛出此异常。文件大小的限制通过 spring.servlet.multipart.max-file-sizespring.servlet.multipart.max-request-size 配置。

以下是 ExceptionHandler 的配置参考。

@ExceptionHandler(value = HttpRequestMethodNotSupportedException.class)
public ResponseBean<String> httpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException ex) {
ResponseBean<String> result = new ResponseBean<>();
result.setSuccess(false);
result.setErrorCode(CommonError.INTERNAL_SERVER_ERROR.getErrorCode());
String message = "Http 请求方法错误,请使用: [" + StringUtils.join(ex.getSupportedHttpMethods(), ",")
+ "] 请求,您当使用的请求方法是:[" + ex.getMethod() + "]";
result.setMessage(message);
log.error(ex.getMessage(), ex);
return result;
}

@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseBean<Map<String, String>> methodArgumentNotValidException(MethodArgumentNotValidException bindException) {
ResponseBean<Map<String, String>> result = ResponseBean.failure(CommonError.ARGUMENTS_ERROR);
Map<String, String> errorMap = new HashMap<>(
bindException.getBindingResult().getFieldErrors().size());

for (FieldError error : bindException.getBindingResult().getFieldErrors()) {
errorMap.put(error.getField(), error.getDefaultMessage());
}
result.setMessage(StringUtils.join(errorMap.values(), ","));
result.setErrorDetails(errorMap);
log.error(bindException.getMessage(), bindException);
return result;
}

@ExceptionHandler(MissingServletRequestParameterException.class)
public ResponseBean<Void> missingServletRequestParameterException(MissingServletRequestParameterException exception) {
ResponseBean<Void> result = ResponseBean.failure(CommonError.ARGUMENTS_ERROR);
result.setMessage("缺少参数:" + exception.getParameterName());
return result;
}

@ExceptionHandler(MaxUploadSizeExceededException.class)
public ResponseBean<String> uploadException(MaxUploadSizeExceededException exception) {
ResponseBean<String> result = ResponseBean.failure(CommonError.SERVICE_ERROR);
result.setMessage(CommonErrorMessages.FILE_LIMIT_ERROR_MSG);
log.error(exception.getMessage(), exception);
return result;
}
java

一般情况下,还需要定义一个拦截所有异常的方法。以避免异常信息直接被抛出到前端,致使前端展示出难以理解的错误信息。

@ExceptionHandler(value = Exception.class)
public ResponseBean<String> otherException(Exception exception) {
ResponseBean<String> result = ResponseBean.failure(CommonError.INTERNAL_SERVER_ERROR);
if (this.coreConfigProperties.getInternalErrorPrefix() != null) {
result.setErrorCode(this.coreConfigProperties.getInternalErrorPrefix() + "_500");
}
result.setMessage(CommonErrorMessages.SYSTEM_ERROR_MSG);
log.error(exception.getMessage(), exception);
return result;
}
java

其中 ResponseBean 是系统中定义的统一应答类。具体代码请参考 《SpringMVC控制层的请求和应答》 章节