一、介绍
正常的Web应用开发时,需要考虑到应用运行发生异常时或出现错误时如何来被处理,例如捕获必要的异常信息,记录日志方便日后排错,友好的用户响应输出等等.而应用程序发生错误,有可能是应用自身的问题,也有可能是客户端操作的问题.在我们的项目中全局异常处理非常重要。
二、自定义异常
对不同的异常类型定义异常类,继承Exception。
* @Description 自定义异常超类
* @Author gc.x
*
*/
public class BaseException extends RuntimeException {
}
/**
* @Description 客户端异常
*/
@Getter
public class BizException extends BaseException {
private CodeEnum codeEnum; //状态码
private String errorMessage; //错误详细信息
public BizException(String errorMessage) {
this.codeEnum = CodeEnum.ILLEGAL_REQUEST;
this.errorMessage = errorMessage;
}
public BizException(CodeEnum codeEnum, String errorMessage) {
this.codeEnum = codeEnum;
this.errorMessage = errorMessage;
}
}
创建自定义状态码枚举类,例如:
三、全局异常处理器
/**
* @Description 全局异常处理器
* @Author gc.x
*/
@Slf4j
@RestControllerAdvice
public class WebExceptionHandler {
/**
* 服务名
*/
@Value("${spring.application.name}")
private String serverName;
/**
* 错误信息前缀
*/
private String errorMessagePrefix;
@PostConstruct
public void init() {
this.errorMessagePrefix = "(" + this.serverName + "服务>) ";
}
/**
* @Description 处理系统内部异常(未知异常, 入空指针, 索引越界)
*/
@ExceptionHandler(value = {Exception.class})
public Object handlerException(Exception e, HttpServletRequest request, HttpServletResponse response) {
log.error("请求路径uri={},系统内部出现异常:{}", request.getRequestURI(), e);
handleFeign(request, response, CodeEnum.SYSTEM_INNER_ERROR);
return ResultSet.error(CodeEnum.SYSTEM_INNER_ERROR, errorMessagePrefix + e.toString(), "服务器繁忙,请稍后再试");
}
/**
* @Description 非法请求异常(SpringAOP)
*/
@ExceptionHandler(value = {
HttpMediaTypeNotAcceptableException.class,
HttpMediaTypeNotSupportedException.class,
HttpRequestMethodNotSupportedException.class,
MissingServletRequestParameterException.class,
NoHandlerFoundException.class,
MissingPathVariableException.class,
HttpMessageNotReadableException.class
})
public ResultSet<?> handlerSpringAOPException(Exception exception, HttpServletRequest request, HttpServletResponse response) {
log.error("非法请求异常:{}", exception.getMessage());
handleFeign(request, response, CodeEnum.ILLEGAL_REQUEST);
return ResultSet.error(CodeEnum.ILLEGAL_REQUEST, errorMessagePrefix + exception.getMessage(), exception.getMessage());
}
/**
* @Description 非法请求异常(@ DateTimeFormat注解抛出异常)
*/
@ExceptionHandler(value = MethodArgumentTypeMismatchException.class)
public ResultSet<?> handlerSpringAOPException(MethodArgumentTypeMismatchException e, HttpServletRequest request, HttpServletResponse response) {
log.error("非法请求异常:{}", e.getMessage());
handleFeign(request, response, CodeEnum.ILLEGAL_REQUEST);
return ResultSet.error(CodeEnum.ILLEGAL_REQUEST, errorMessagePrefix + e.getMessage(), e.getMessage());
}
@ExceptionHandler(DeleteException.class)
public ResultSet<?> handleDeleteException(DeleteException e, HttpServletRequest request, HttpServletResponse response) {
log.error("删除验证失败:{}", e.getDeleteValidFailures());
handleFeign(request, response, CodeEnum.DELETE_VALID);
return ResultSet.custom(CodeEnum.DELETE_VALID, e.getDeleteValidFailures());
}
@ExceptionHandler(BatchException.class)
public ResultSet<?> handleBatchException(BatchException e, HttpServletRequest request, HttpServletResponse response) {
log.error("批量操作失败:{}", e.getBatchInfo());
handleFeign(request, response, CodeEnum.BATCH_OP);
return ResultSet.custom(CodeEnum.BATCH_OP, e.getBatchInfo());
}
/**
* @Description 非法请求(处理spring validation参数校验抛出异常1)
*/
@ExceptionHandler(value = {MethodArgumentNotValidException.class})
public ResultSet<?> handlerMethodArgumentNotValidException(MethodArgumentNotValidException e, HttpServletRequest request, HttpServletResponse response) {
//获取异常字段及对应的异常信息
StringBuilder stringBuilder = new StringBuilder();
Class<?> target = e.getBindingResult().getTarget().getClass();
e.getBindingResult().getAllErrors().stream()
.map(error -> {
if (error instanceof FieldError) {
FieldError t = (FieldError) error;
ApiModelProperty property = null;
try {
property = target.getDeclaredField(t.getField()).getAnnotation(ApiModelProperty.class);
} catch (NoSuchFieldException noSuchFieldException) {
noSuchFieldException.printStackTrace();
}
String name = (property == null ? t.getField() : Optional.of(property.value()).orElse(t.getField()));
return "[" + name + "]-[" + t.getRejectedValue() + "]=>" + t.getDefaultMessage() + " ";
} else {
return error.getDefaultMessage();
}
}).forEach(stringBuilder::append);
String errorMessage = stringBuilder.toString();
log.error("请求参数异常:{}", errorMessage);
handleFeign(request, response, CodeEnum.ILLEGAL_REQUEST);
return ResultSet.error(CodeEnum.ILLEGAL_REQUEST, errorMessagePrefix + errorMessage, errorMessage);
}
/**
* @Description 非法请求异常(处理spring validation参数校验抛出异常2)
*/
@ExceptionHandler(value = {ConstraintViolationException.class})
public ResultSet<?> handlerConstraintViolationException(ConstraintViolationException e, HttpServletRequest request, HttpServletResponse response) {
String errorMessage = e.getLocalizedMessage();
log.error("非法请求异常:{}", errorMessage);
handleFeign(request, response, CodeEnum.ILLEGAL_REQUEST);
return ResultSet.error(CodeEnum.ILLEGAL_REQUEST, errorMessagePrefix + e.getMessage(), errorMessage);
}
/**
* @Description 处理业务异常
*/
@ExceptionHandler(value = {BizException.class})
public ResultSet<?> handlerCustomException(BizException e, HttpServletRequest request, HttpServletResponse response) {
String errorMessage = e.getErrorMessage();
log.error("客户端异常:{}", errorMessage);
handleFeign(request, response, e.getCodeEnum());
return ResultSet.error(e.getCodeEnum(), errorMessagePrefix + errorMessage, errorMessage);
}
/**
* @Description 处理系统异常
*/
@ExceptionHandler(value = {ServerException.class})
public ResultSet<?> handlerServerException(ServerException e, HttpServletRequest request, HttpServletResponse response) {
String errorMessage = e.getErrorMessage();
log.error("服务端异常:{}", errorMessage);
handleFeign(request, response, e.getCodeEnum());
return ResultSet.error(e.getCodeEnum(), errorMessagePrefix + e.getMessage(), errorMessage);
}
// 处理feign调用时异常,不进行返回封装,按原内容直接抛出
private void handleFeign(HttpServletRequest request, HttpServletResponse response, CodeEnum codeEnum) {
if (StringUtils.isNotEmpty(request.getHeader(Global.FEIGN))) {
response.setStatus(codeEnum.getCode());
}
}
}