前言
springboot实现自定义全局异常处理,以及统一返回数据。
1、分析
首先,实现全局异常的流程
从图中可以看到,实现全局异常会需要这样几个类:
- 自定义异常接口类
- 自定义异常枚举类
- 自定义异常类
- 自定义异常处理类
- 自定义全局响应类
2、创建所需类
2.1、为了代码解耦,创建一个接口类出来,定义自定义接口所需要的方法
/** * Http状态信息接口 */ public interface HttpStatusInfoInterface { int getCode(); String getMessage(); }
2.2、定义一个枚举类,实现上述接口,重写上述接口的两个方法来操作这个枚举类内部的各个具体枚举值
后续方便管理所有错误枚举的错误信息以及code码,通过构造方法传入code值和message或者直接传入一个枚举值都行
/** * Http状态码 */ public enum HttpStatusEnum implements HttpStatusInfoInterface{ //定义状态枚举值 SUCCESS(200 , "成功!"), BODY_NOT_MATCH(400 , "数据格式不匹配!"), NOT_FOUND(404 , "访问资源不存在!"), INTERNAM_SERVER_ERROR(500 , "服务器内部错误!"), SERVER_BUSY(503 , "服务器正忙,请稍后再试!"), REQUEST_METHOD_SUPPORT_ERROR(10001 , "当前请求方法不支持!"), REQUEST_DATA_NULL(10002 , "当前请求参数为空!"), USER_NOT_EXISTS(10003 , "该用户不存在!"), USER_INVALID(10004 , "当前登录信息已生效,请重新登录!"), PASSWORD_ERROR(10005 , "密码错误!"), USER_NAME_LOCK(10006 , "该账号已被锁定!"); //状态码 private int code; //提示信息 private String message; //构造方法 HttpStatusEnum(int code , String message) { this.code = code; this.message = message; } @Override public int getCode() { return this.code; } @Override public String getMessage() { return this.message; } }
2.3、自定义一个异常类
就像空指针异常类、IO流异常类一样。此处自定义的异常类属于异常类,所有肯定是要继承一个异常类的,此处需要继承RuntimeException,原因如下:
RuntimeException相比Exception来讲,他是在程序运行时才会爆出异常,在编译时是不会出现异常的,这就表示,如果你throw了一个RuntimeException,不需要做额外操作;而throw一个Exception,程序会要求你try-catch,否则你根本启动不了程序,程序会提示(必须对其进行捕获或声明以便抛出)
import com.xxxx.springbootmybatis.common.HttpStatusEnum; import lombok.Data; @Data public class HttpException extends RuntimeException{ //错误码 private int code; //错误信息 private String message; // 默认构造函数 public HttpException() { super(); } public HttpException(HttpStatusEnum httpStatusEnum) { super(String.valueOf(httpStatusEnum.getCode())); this.code = httpStatusEnum.getCode(); this.message = httpStatusEnum.getMessage(); } }
2.4、封装统一返回类
封装返回值类BaseResponse类和RespGenerator类都是属于规范方法返回值结构体的类,也有利于一致化后端所有接口的返回结构,方便前端读取所需要的数据。
HttpResult类:规定返回值结构。
HttpResultGenerator类:将逻辑处理后的数据包装转换成HttpResult类进行返回给前端。
import lombok.Data; /** * http 统一返回类 * @param <T> */ @Data public class HttpResult<T> { private Integer code; private String message; private T data; //构造方法 public HttpResult(Integer code, String message, T data) { this.code = code; this.message = message; this.data = data; } //默认构造函数 public HttpResult() { super(); } }
public class HttpResultGenerator { //正常返回时调用方法 public static HttpResult success(Object data) { return new HttpResult(HttpStatusEnum.SUCCESS.getCode() , "接口调用成功!" , data); } //失败时调用方法(入参是异常枚举) public static HttpResult fail(HttpStatusEnum httpStatusEnum) { return new HttpResult(httpStatusEnum.getCode() , httpStatusEnum.getMessage() , null); } //失败时调用方法(提供给GlobalExceptionHandler类使用) public static HttpResult fail(int code , String message) { return new HttpResult(code , message , null); } }
2.5、自定义异常处理类
@RestControllerAdvice注解是@ResponseBody和@ControllerAdvice的组合。
@ResponseBody注解:通常用来将java对象转成JSON对象,返回给前端JSON数据。
@ControllerAdvice注解:结合方法型注解@ExceptionHandler,用于捕获Controller中抛出的指定类型的异常,从而达到不同类型的异常区别处理的目的。
@ExceptionHandler注解统一处理某一类异常,从而能够减少代码重复率和复杂度,value值为什么异常类型,就处理什么异常类型的逻辑。
import lombok.extern.slf4j.Slf4j; import com.xxxx.springbootmybatis.common.HttpResult; import com.xxxx.springbootmybatis.common.HttpResultGenerator; import com.xxxx.springbootmybatis.common.HttpStatusEnum; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; /** * 自定义异常处理类 */ @Slf4j @RestControllerAdvice public class GlobalExceptionHandler { //处理自定义异常 @ExceptionHandler(value = HttpException.class) public HttpResult<Object> baseExceptionHandler(HttpException e) { log.error("发生业务异常!原因是:{}" , e.getMessage()); return HttpResultGenerator.fail(e.getCode() , e.getMessage()); } //处理空指针异常 @ExceptionHandler(value = NullPointerException.class) public HttpResult<Object> exceptionHandler(Exception e) { log.error("发生异常!原因是:{}" , e); return HttpResultGenerator.fail(HttpStatusEnum.INTERNAM_SERVER_ERROR); } }
3、测试
新建一个测试controller。
3.1、测试自定义异常全局处理效果
@Slf4j @RestController @CrossOrigin("*") @RequestMapping("/user") public class UserController { @RequestMapping("/loginTest") public HttpResult loginTest(@RequestParam(value="name" ,required=false) String userName, @RequestParam(value="pwd" ) String password ) { // URL: http://127.0.0.1/user/loginone?userName=zs&pwd=123 log.info("userName:{} , password:{}" , userName , password); if(StringUtils.isEmpty(userName)) { throw new HttpException(HttpStatusEnum.USER_NOT_EXISTS); } else { return HttpResultGenerator.success("登录校验成功"); } } }
3.2、用postman测试,若username没有传值,会抛出自定义异常
3.3、控制台结果
2023-04-19 17:47:51.411 |-INFO [http-nio-80-exec-3] com.hqyj.springbootmybatis.controller.UserController [54] -| userName: , password:123 2023-04-19 17:47:51.411 |-ERROR [http-nio-80-exec-3] com.hqyj.springbootmybatis.common.exception.GlobalExceptionHandler [21] -| 发生业务异常!原因是:该用户不存在!