实际业务中的异常处理(全局异常拦截器)
之前在写问卷系统的时候我们也会偶尔遇到异常,比如说要查询一个数据库,但是数据为空的情况。
我们在Controller层中还要进行逻辑判断
比如说这里,我们要根据Service接口返回的数据来判断用户名是否被使用即是否存在于数据库,这样就是在Controller层中进行的逻辑判断
其实绝大多数情况我们都可以直接返回成功的结果,若出现问题,应该在Service中抛出异常并返回问题数据
这里就需要用到自定异常和全局异常拦截器
自定异常
我们可以将一个系统的异常比较笼统地分为三种异常:客户端异常ClientException、服务端异常ServiceException和远程调用异常RemoteException
他们都继承于一个抽象异常类AbstractException,我们先编写一个AbstractException
/**
* 自定抽象异常
* @see ClientException
* @see ServiceException
* @see RemoteException
*/
@Getter
public abstract class AbstractException extends RuntimeException{
public final String errorCode;
public final String errorMessage;
//这里的设计:我们的自定异常都会去实现IErrorCode,所以我们这里在实现的时候直接传入一个自定Exception类,从里面获取错误码
public AbstractException(String message, Throwable cause, IErrorCode errorCode) {
//先调用RuntimeException的初始化方法
super(message, cause);
//再设置异常码
this.errorCode = errorCode.code();
//若异常message不为空,就赋值给errorMessage
this.errorMessage = Optional.ofNullable(StringUtils.hasLength(message) ? message : null).orElse(errorCode.message());
}
}
然后逐个配置三种自定抽象异常
/**
* 客户端异常
*/
public class ClientException extends AbstractException{
public ClientException(IErrorCode errorCode){
this(null, null, errorCode);
}
public ClientException(String message){
this(message, null, BaseErrorCode.CLIENT_ERROR);
}
public ClientException(String message,IErrorCode errorCode){
this(message, null, errorCode);
}
public ClientException(String message, Throwable cause, IErrorCode errorCode) {
super(message, cause, errorCode);
}
@Override
public String toString() {
return "ClientException{" +
"errorCode='" + errorCode + '\'' +
", errorMessage='" + errorMessage + '\'' +
'}';
}
}
/**
* 远程服务调用异常
*/
public class RemoteException extends AbstractException{
public RemoteException(String message) {
super(message, null, BaseErrorCode.REMOTE_ERROR);
}
public RemoteException(IErrorCode errorCode) {
super(errorCode.message(), null, errorCode);
}
public RemoteException(String message,IErrorCode errorCode) {
super(message, null, errorCode);
}
public RemoteException(String message, Throwable cause, IErrorCode errorCode) {
super(message, cause, errorCode);
}
@Override
public String toString() {
return "RemoteException{" +
"errorCode='" + errorCode + '\'' +
", errorMessage='" + errorMessage + '\'' +
'}';
}
}
/**
* 服务端异常
*/
public class ServiceException extends AbstractException{
public ServiceException(String message) {
this(message, null, BaseErrorCode.SERVICE_ERROR);
}
public ServiceException(IErrorCode errorCode) {
this(errorCode.message(), null, errorCode);
}
public ServiceException(String message,IErrorCode errorCode) {
this(message, null, errorCode);
}
public ServiceException(String message, Throwable cause, IErrorCode errorCode) {
super(message, cause, errorCode);
}
@Override
public String toString() {
return "ServiceException{" +
"errorCode='" + errorCode + '\'' +
", errorMessage='" + errorMessage + '\'' +
'}';
}
}
全局返回对象Result
我们相应前端的请求最好有一个统一的格式,所以这里需要定义一个全局返回对象,它必须是可序列化的
/**
* 全局返回对象
*/
@Data
@Accessors(chain = true)
public class Result<T> implements Serializable {
@Serial
private static final long serialVersionUID = 5679018624309023727L;
/**
* 正确的返回码
*/
public static final String SUCCESS_CODE = "0";
/**
* 返回码
*/
private String code;
/**
* 返回消息
*/
private String message;
/**
* 相应数据
*/
private T data;
/**
* 请求id
*/
private String requestId;
/**
* 判断是否是成功请求
* @return 是否成功
*/
public boolean isSuccess(){
return SUCCESS_CODE.equals(code);
}
}
为了更便捷地使用Result,我们再编写一个工具类Results
/**
* 全局返回对象构造器
* 用于构造Result
*/
public class Results {
/**
* 构造成功相应
*/
public static Result<Void> success(){
return new Result<Void>()
.setCode(Result.SUCCESS_CODE);
}
/**
* 构造代返回数据的成功相应
*/
public static <T> Result<T> success(T data){
return new Result<T>()
.setCode(Result.SUCCESS_CODE)
.setData(data);
}
/**
* 构造服务端失败相应
*/
public static Result<Void> failure(){
return new Result<Void>()
.setCode(BaseErrorCode.SERVICE_ERROR.code())
.setMessage(BaseErrorCode.SERVICE_ERROR.message());
}
/**
* 通过 {@link AbstractException} 构建失败响应
*/
public static Result<Void> failure(AbstractException abstractException){
String errorCode = Optional.ofNullable(abstractException.getErrorCode()).orElse(BaseErrorCode.SERVICE_ERROR.code());
String errorMessage = Optional.ofNullable(abstractException.getErrorMessage()).orElse(BaseErrorCode.SERVICE_ERROR.message());
return new Result<Void>()
.setCode(errorCode)
.setMessage(errorMessage);
}
/**
* 通过 errorCode、errorMessage 构建失败响应
*/
public static Result<Void> failure(String errorCode, String errorMessage) {
return new Result<Void>()
.setCode(errorCode)
.setMessage(errorMessage);
}
}
全局异常拦截器
有了异常,有了固定的返回格式,这下方便了
为了从Controller中剔除业务逻辑,我们可以直接在ServiceImplement中进行判断,如果出问题就抛出异常,若全局异常拦截器捕捉到了异常,就在控制台上打印对应错误信息和返回对应的Result
/**
* 全局异常处理器
*
*/
@Component
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 拦截参数验证异常
*/
@SneakyThrows
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public Result validExceptionHandler(HttpServletRequest request, MethodArgumentNotValidException ex) {
BindingResult bindingResult = ex.getBindingResult();
FieldError firstFieldError = CollectionUtil.getFirst(bindingResult.getFieldErrors());
String exceptionStr = Optional.ofNullable(firstFieldError)
.map(FieldError::getDefaultMessage)
.orElse(StrUtil.EMPTY);
log.error("[{}] {} [ex] {}", request.getMethod(), getUrl(request), exceptionStr);
return Results.failure(BaseErrorCode.CLIENT_ERROR.code(), exceptionStr);
}
/**
* 拦截应用内抛出的异常
*/
@ExceptionHandler(value = {AbstractException.class})
public Result abstractException(HttpServletRequest request, AbstractException ex) {
if (ex.getCause() != null) {
log.error("[{}] {} [ex] {}", request.getMethod(), request.getRequestURL().toString(), ex.toString(), ex.getCause());
return Results.failure(ex);
}
log.error("[{}] {} [ex] {}", request.getMethod(), request.getRequestURL().toString(), ex.toString());
return Results.failure(ex);
}
/**
* 拦截未捕获异常
*/
@ExceptionHandler(value = Throwable.class)
public Result defaultErrorHandler(HttpServletRequest request, Throwable throwable) {
log.error("[{}] {} ", request.getMethod(), getUrl(request), throwable);
return Results.failure();
}
private String getUrl(HttpServletRequest request) {
if (StringUtils.isEmpty(request.getQueryString())) {
return request.getRequestURL().toString();
}
return request.getRequestURL().toString() + "?" + request.getQueryString();
}
}
我们在Service中抛出异常
异常拦截器就会接管并且直接返回错误给Controller
Controller也由原来的
简化为
舒服了,其实之前的问卷项目里就老是在纠结这个问题。
明明Service没有完成操作,但是我还要在Controller来进行判断,有时返回的还是success()。现在通过异常拦截器统一处理,就没有这种问题了