什么是异常
异常是在程序运行过程中可能遇到的不正常情况,异常的出现可能导致程序出现预期之外的行为,或者直接中止程序。在 Java 中,异常分为 Exception 和 Error ,这两个异常有一个共同的祖先 Throwable 类。
Exception 是指程序中可能出现的可以被处理的异常,通常使用 try / catch 进行捕获处理。Exception 又可以分为受检查异常和不受检查异常。
Error 是指程序无法处理的异常,比如虚拟机内存不足,虚拟机运行错误等异常。
受检查异常
受检查异常是在编译时期就需要进行处理的异常,通常是外部因素引起,比如找不到文件,网络连接失败等。必须通过 try / catch 或者 throws 进行捕获处理抛出,否则程序代码无法通过编译。
不受检查异常
不受检查异常,也成为运行时异常,一般是因为程序逻辑错误引起,比如常见的空指针异常、数组 index 越界等边界问题。虽然这类异常不强制进行处理,但是如果出现还是可能导致程序出现预期之外的问题。所以最好还是进行捕获处理。
什么是全局异常处理器,为什么要自定义全局异常处理器
在写代码的过程中,常常会因为需要提前处理上述的异常,就会出现大量的 try / catch 语句,影响代码的可读性。此时使用全局异常处理器就能减少这些冗余的代码。
自定义全局异常处理器,可以更清楚返回给客户端异常的原因即相关描述,更加个性化以及更加灵活。
自定义全局异常处理器
相关注解
@ControllerAdvice 学名是 Controller 增强器,作用是给 Controller 控制器添加统一的操作和处理。结合 @ExceptionHandler 实现全局异常处理。
@ExceptionHandler 用于处理应用程序中的异常,当程序发生异常时,该注解会将其拦截并处理,并返回结果给前端。
@RestControllerAdvice 相当于 @ControllerAdvice 和 @RequestBody 的结合。
实现
以下实现自己使用习惯,如有错误烦请指出。
1. 自定义异常信息枚举类
自定义异常类,个性化得返回异常的状态码,异常信息,异常描述给前端。
public enum ErrorCode {
SUCCESS(0,"OK",""),
PARAMS_ERROR(40000,"请求参数错误",""),
NULL_ERROR(40001,"请求数据为空",""),
NOT_LOGIN(40100,"未登录",""),
NO_AUTH(40101,"无权限",""),
SYSTEM_ERROR(50000,"系统内部异常","");
/**
* 状态码
*/
private int code;
/**
* 状态码信息
*/
private final String message;
/**
* 状态码描述
*/
private final String description;
ErrorCode(int code, String message, String description) {
this.code = code;
this.message = message;
this.description = description;
}
//枚举类不能使用 @Data 注解生成 get方法
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
public String getDescription() {
return description;
}
}
2. 自定义全局异常类
自定义异常类,当抛出异常的时候直接调用这个异常,然后通过这个异常获取到自定义的枚举类异常信息,或者自定义异常的描述等。
import com.pb.demo.common.ErrorCode;
/**
* 自定义异常类,继承 RuntimeException 类中的异常信息属性
*/
public class BusinessException extends RuntimeException{
/**
* 异常状态码
*
*/
private final int code;
/**
*异常状态描述
*
*/
private final String description;
/**
*构造方法,自定义异常信息,异常状态码,异常描述
*
*/
public BusinessException(String message, int code, String description) {
super(message);
this.code = code;
this.description = description;
}
/**
*构造方法,直接使用异常状态枚举类中的信息
*
*/
public BusinessException(ErrorCode errorCode) {
super(errorCode.getMessage());
this.code = errorCode.getCode();
this.description = errorCode.getDescription();
}
/**
*构造方法,使用枚举类中的状态码和信息,自定义描述,比如输入的字数超过了20个
*
*/
public BusinessException(ErrorCode errorCode,String description) {
super(errorCode.getMessage());
this.code = errorCode.getCode();
this.description = description;
}
public int getCode() {
return code;
}
public String getDescription() {
return description;
}
}
3. 自定义通用响应类
在自定义全局异常处理器的同时,可以自定义一个通用的响应类,目的是将异常的信息封装成一个固定的格式,用于返回给前端固定格式的信息,更方便前端处理信息。
import lombok.Data;
import java.io.Serializable;
/**
* 通用响应类
* @param <T>
*/
@Data
public class BaseResponse<T> implements Serializable {
/**
* 响应码
*/
private int code;
/**
* 响应数据,比如 id ,json 格式的信息等
*/
private T data;
/**
* 响应信息
*/
private String message;
/**
* 响应描述
*/
private String description;
/*
*构造函数,自定义响应码、响应信息、响应数据、响应描述
*/
public BaseResponse(int code, T data, String message,String description) {
this.code = code;
this.data = data;
this.message = message;
this.description = description;
}
/*
*构造函数,自定义响应码、响应信息、响应数据,响应描述为空
*/
public BaseResponse(int code, T data,String message) {
this.code = code;
this.data = data;
this.message = message;
this.description="";
}
/*
*构造函数,直接返回自定义异常枚举类的信息
*/
public BaseResponse(ErrorCode errorCode){
//code, data, message, description
this(errorCode.getCode(), null,
errorCode.getMessage(),errorCode.getDescription());
}
}
4. 自定义通用返回类
配合通用响应类使用,将返回的信息封装成通用响应类的格式
/*
*自定义通用返回类
*/
public class ResultUtils {
/*
*静态泛型方法,直接返回成功,以及传入的数据 data
*/
public static <T> BaseResponse<T> success(T data){
return new BaseResponse<>(0,data,"ok","");
}
/*
*返回异常信息
*/
public static BaseResponse error(ErrorCode errorCode){
return new BaseResponse<>(errorCode);
}
public static BaseResponse error(ErrorCode errorCode,String message,String description) {
return new BaseResponse<>(errorCode.getCode(),null,message,description);
}
public static BaseResponse error(int code,String message,String description){
return new BaseResponse<>(code,null,message,description);
}
}
5. 自定义全局异常处理器
package com.pb.demo.exception;
import com.pb.demo.common.BaseResponse;
import com.pb.demo.common.ErrorCode;
import com.pb.demo.common.ResultUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
全局异常处理器
@author p'b
*/
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {
/*
*捕获自定义的异常,并通过自定义通用返回类进行返回异常信息
*/
@ExceptionHandler(BusinessException.class) //方法只捕获参数内的异常
public BaseResponse businessExceptionHandler(BusinessException e){
log.error("businessException: "+ e.getMessage(),e); //集中记录日志
return ResultUtils.error(e.getCode(),e.getMessage(),e.getDescription());
}
/*
*捕获运行时的异常,并通过自定义通用返回类进行返回异常信息
*/
@ExceptionHandler(RuntimeException.class)
public BaseResponse runtimeExceptionHandler(RuntimeException r){
log.error("runtimeException: ",r); //集中记录日志
return ResultUtils.error(ErrorCode.SYSTEM_ERROR,r.getMessage(),"");
}
}
调用举例
throw new BusinessException(ErrorCode.PARAMS_ERROR, "密码设置不正确");
抛出密码错误的异常,直接使用枚举类中的的状态码的信息,然后自定义全局异常处理器捕获到该异常以及异常信息,通过通用返回类返回给响应类进行封装,最后以固定的格式响应给前端。前端能更清晰获取到信息以及处理信息。
以上内容均是自己使用时的习惯,部分代码方法没有使用到,可以删除或者自定义其他形式的返回格式,毕竟构造方法可以创建很多个。
如果哪里有错误烦请指出,我一直保持学习。