SpringBoot教程(二十七) | SpringBoot集成AOP实现异常处理
- 前言
- 第一步:统一接口返回结果
- 1. 统一封装结果包含如下参数
- 2. 创建 枚举HttpStatusEnum(返回结果代码)
- 3. 创建 ResponseResult (返回实体类)
- 第二步:配置全局异常处理
- 1. 创建 GlobalException (全局异常处理类)
- 2. 自定义异常类(用于主动返回业务性错误)
- 3. 如何使用自定义的异常类
- 1. 使用 @RestControllerAdvice
- 2. 不使用 @RestControllerAdvice
前言
捕获全局异常后进行相关处理后,肯定是需要返回对象给前端的。
为了统一规范化,提高交互的规范性及通用性 及 前后端联调效率。
避免一个接口一个返回格式的问题。
所以需要完成以下两步
1.统一接口返回结果
2.配置全局异常处理
第一步:统一接口返回结果
1. 统一封装结果包含如下参数
- 状态码:code
- 状态信息:status
- 返回信息:message
- 数据:data
2. 创建 枚举HttpStatusEnum(返回结果代码)
import lombok.Getter;
/**
* Http状态返回枚举
*
**/
@Getter
public enum HttpStatusEnum {
/**
* 操作成功
*/
SUCCESS(200, "操作成功"),
/**
* 对象创建成功
*/
CREATED(201, "对象创建成功"),
/**
* 请求已经被接受
*/
ACCEPTED(202, "请求已经被接受"),
/**
* 操作已经执行成功,但是没有返回数据
*/
NO_CONTENT(204, "操作已经执行成功,但是没有返回数据"),
/**
* 资源已被移除
*/
MOVED_PERM(301, "资源已被移除"),
/**
* 重定向
*/
SEE_OTHER(303, "重定向"),
/**
* 资源没有被修改
*/
NOT_MODIFIED(304, "资源没有被修改"),
/**
* 参数列表错误(缺少,格式不匹配)
*/
BAD_REQUEST(400, "参数列表错误(缺少,格式不匹配)"),
/**
* 未授权
*/
UNAUTHORIZED(401, "未授权"),
/**
* 访问受限,授权过期
*/
FORBIDDEN(403, "访问受限,授权过期"),
/**
* 资源,服务未找到
*/
NOT_FOUND(404, "资源,服务未找!"),
/**
* 不允许的http方法
*/
BAD_METHOD(405, "不允许的http方法"),
/**
* 资源冲突,或者资源被锁
*/
CONFLICT(409, "资源冲突,或者资源被锁"),
/**
* 不支持的数据,媒体类型
*/
UNSUPPORTED_TYPE(415, "不支持的数据,媒体类型"),
/**
* 系统内部错误
*/
ERROR(500, "系统内部错误"),
/**
* 接口未实现
*/
NOT_IMPLEMENTED(501, "接口未实现"),
/**
* 系统警告消息
*/
WARN(601,"系统警告消息");
private final Integer code;
private final String message;
HttpStatusEnum(Integer code, String message) {
this.code = code;
this.message = message;
}
public Integer getCode() {
return code;
}
public String getMessage() {
return message;
}
}
3. 创建 ResponseResult (返回实体类)
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 返回结果集
*
**/
@Data
//生成一个包含所有字段的构造函数
@AllArgsConstructor
//生成一个无参构造函数
@NoArgsConstructor
public class ResponseResult<T> {
/**
* 状态码
*/
//@ApiModelProperty(value = "状态码")
private Integer code;
/**
* 状态信息
*/
//@ApiModelProperty(value = "状态信息")
private Boolean status;
/**
* 返回信息
*/
//@ApiModelProperty(value = "返回信息")
private String message;
/**
* 数据
*/
//@ApiModelProperty(value = "数据")
private T data;
/**
* 全参数方法
*
* @param code 状态码
* @param status 状态
* @param message 返回信息
* @param data 返回数据
* @param <T> 泛型
* @return {@link ResponseResult<T>}
*/
private static <T> ResponseResult<T> response(Integer code, Boolean status, String message, T data) {
ResponseResult<T> responseResult = new ResponseResult<>();
responseResult.setCode(code);
responseResult.setStatus(status);
responseResult.setMessage(message);
responseResult.setData(data);
return responseResult;
}
/**
* 全参数方法
*
* @param code 状态码
* @param status 状态
* @param message 返回信息
* @param <T> 泛型
* @return {@link ResponseResult<T>}
*/
private static <T> ResponseResult<T> response(Integer code, Boolean status, String message) {
ResponseResult<T> responseResult = new ResponseResult<>();
responseResult.setCode(code);
responseResult.setStatus(status);
responseResult.setMessage(message);
return responseResult;
}
/**
* 成功返回(无参)
*
* @param <T> 泛型
* @return {@link ResponseResult<T>}
*/
public static <T> ResponseResult<T> success() {
return response(HttpStatusEnum.SUCCESS.getCode(), true, HttpStatusEnum.SUCCESS.getMessage(), null);
}
/**
* 成功返回(枚举参数)
*
* @param httpResponseEnum 枚举参数
* @param <T> 泛型
* @return {@link ResponseResult<T>}
*/
public static <T> ResponseResult<T> success(HttpStatusEnum httpResponseEnum) {
return response(httpResponseEnum.getCode(), true, httpResponseEnum.getMessage());
}
/**
* 成功返回(状态码+返回信息)
*
* @param code 状态码
* @param message 返回信息
* @param <T> 泛型
* @return {@link ResponseResult<T>}
*/
public static <T> ResponseResult<T> success(Integer code, String message) {
return response(code, true, message);
}
/**
* 成功返回(返回信息 + 数据)
*
* @param message 返回信息
* @param data 数据
* @param <T> 泛型
* @return {@link ResponseResult<T>}
*/
public static <T> ResponseResult<T> success(String message, T data) {
return response(HttpStatusEnum.SUCCESS.getCode(), true, message, data);
}
/**
* 成功返回(状态码+返回信息+数据)
*
* @param code 状态码
* @param message 返回信息
* @param data 数据
* @param <T> 泛型
* @return {@link ResponseResult<T>}
*/
public static <T> ResponseResult<T> success(Integer code, String message, T data) {
return response(code, true, message, data);
}
/**
* 成功返回(数据)
*
* @param data 数据
* @param <T> 泛型
* @return {@link ResponseResult<T>}
*/
public static <T> ResponseResult<T> success(T data) {
return response(HttpStatusEnum.SUCCESS.getCode(), true, HttpStatusEnum.SUCCESS.getMessage(), data);
}
/**
* 成功返回(返回信息)
*
* @param message 返回信息
* @param <T> 泛型
* @return {@link ResponseResult<T>}
*/
public static <T> ResponseResult<T> success(String message) {
return response(HttpStatusEnum.SUCCESS.getCode(), true, message, null);
}
/**
* 失败返回(无参)
*
* @param <T> 泛型
* @return {@link ResponseResult<T>}
*/
public static <T> ResponseResult<T> fail() {
return response(HttpStatusEnum.ERROR.getCode(), false, HttpStatusEnum.ERROR.getMessage(), null);
}
/**
* 失败返回(枚举)
*
* @param httpResponseEnum 枚举
* @param <T> 泛型
* @return {@link ResponseResult<T>}
*/
public static <T> ResponseResult<T> fail(HttpStatusEnum httpResponseEnum) {
return response(httpResponseEnum.getCode(), false, httpResponseEnum.getMessage());
}
/**
* 失败返回(状态码+返回信息)
*
* @param code 状态码
* @param message 返回信息
* @param <T> 泛型
* @return {@link ResponseResult<T>}
*/
public static <T> ResponseResult<T> fail(Integer code, String message) {
return response(code, false, message);
}
/**
* 失败返回(返回信息+数据)
*
* @param message 返回信息
* @param data 数据
* @param <T> 泛型
* @return {@link ResponseResult<T>}
*/
public static <T> ResponseResult<T> fail(String message, T data) {
return response(HttpStatusEnum.ERROR.getCode(), false, message, data);
}
/**
* 失败返回(状态码+返回信息+数据)
*
* @param code 状态码
* @param message 返回消息
* @param data 数据
* @param <T> 泛型
* @return {@link ResponseResult<T>}
*/
public static <T> ResponseResult<T> fail(Integer code, String message, T data) {
return response(code, false, message, data);
}
/**
* 失败返回(数据)
*
* @param data 数据
* @param <T> 泛型
* @return {@link ResponseResult<T>}
*/
public static <T> ResponseResult<T> fail(T data) {
return response(HttpStatusEnum.ERROR.getCode(), false, HttpStatusEnum.ERROR.getMessage(), data);
}
/**
* 失败返回(返回信息)
*
* @param message 返回信息
* @param <T> 泛型
* @return {@link ResponseResult<T>}
*/
public static <T> ResponseResult<T> fail(String message) {
return response(HttpStatusEnum.ERROR.getCode(), false, message, null);
}
}
第二步:配置全局异常处理
1. 创建 GlobalException (全局异常处理类)
关键注解:@RestControllerAdvice 可以直接使用,不需要与@Configuration配合使用
package com.example.reactboot.aop;
import cn.hutool.core.util.ObjectUtil;
import com.example.reactboot.Exception.ServiceException;
import com.example.reactboot.zdyresult.HttpStatusEnum;
import com.example.reactboot.zdyresult.ResponseResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.validation.BindException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.servlet.http.HttpServletRequest;
import java.nio.file.AccessDeniedException;
/**
* 异常处理 配置
*
* @author javadog
*/
@RestControllerAdvice
public class GlobalException {
private static final Logger log = LoggerFactory.getLogger(GlobalException.class);
/**
* 权限校验异常
*/
@ExceptionHandler(AccessDeniedException.class)
public ResponseResult handleAccessDeniedException(AccessDeniedException e, HttpServletRequest request) {
String requestURI = request.getRequestURI();
log.error("请求地址'{}',权限校验失败'{}'", requestURI, e.getMessage());
return ResponseResult.fail(HttpStatusEnum.FORBIDDEN.getCode(), HttpStatusEnum.FORBIDDEN.getMessage());
}
/**
* 请求方式不支持
*/
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public ResponseResult handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException e,
HttpServletRequest request) {
String requestURI = request.getRequestURI();
log.error("请求地址'{}',不支持'{}'请求", requestURI, e.getMethod());
return ResponseResult.fail(e.getMessage());
}
/**
* 业务异常(自定义的)
*/
@ExceptionHandler(BusinessException.class)
public ResponseResult handleServiceException(BusinessException e) {
log.error(e.getMessage(), e);
Integer code = e.getCode();
return ObjectUtil.isNotNull(code) ? ResponseResult.fail(code, e.getMessage()) : ResponseResult.fail(e.getMessage());
}
/**
* 拦截未知的运行时异常
*/
@ExceptionHandler(RuntimeException.class)
public ResponseResult handleRuntimeException(RuntimeException e, HttpServletRequest request) {
String requestURI = request.getRequestURI();
log.error("请求地址'{}',发生未知异常.", requestURI, e);
return ResponseResult.fail(e.getMessage());
}
/**
* 系统异常
*/
@ExceptionHandler(Exception.class)
public ResponseResult handleException(Exception e, HttpServletRequest request) {
String requestURI = request.getRequestURI();
log.error("请求地址'{}',发生系统异常.", requestURI, e);
return ResponseResult.fail(e.getMessage());
}
/**
* 数据绑定异常
*/
@ExceptionHandler(BindException.class)
public ResponseResult handleBindException(BindException e) {
log.error(e.getMessage(), e);
String message = e.getAllErrors().get(0).getDefaultMessage();
return ResponseResult.fail(message);
}
/**
* 方法参数验证失败异常
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public Object handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
log.error(e.getMessage(), e);
String message = e.getBindingResult().getFieldError().getDefaultMessage();
return ResponseResult.fail(message);
}
}
2. 自定义异常类(用于主动返回业务性错误)
主动返回业务性错误,错误状态及描述想自由发挥。
就可以使用这个自定义的异常来操作
package com.example.reactboot.Exception;
/**
* 自定义的服务异常类
*/
public class BusinessException extends RuntimeException {
private static final long serialVersionUID = 1L; // 用于序列化
private Integer code; // 错误码字段
// 构造函数,可以接收一个消息字符串和一个错误码
public ServiceException(String message, Integer code) {
super(message);
this.code = code;
}
// 构造函数,只接收一个消息字符串
public ServiceException(String message) {
this(message, null); // 默认错误码为null,表示没有提供
}
// 构造函数,接收一个消息字符串和一个原因(Throwable),以及一个错误码
public ServiceException(String message, Throwable cause, Integer code) {
super(message, cause);
this.code = code;
}
// 构造函数,只接收一个原因(Throwable)和一个错误码
public ServiceException(Throwable cause, Integer code) {
super(cause);
this.code = code;
}
// getter方法
public Integer getCode() {
return code;
}
// setter方法(如果需要的话,但通常异常对象一旦创建就不应该修改其状态)
public void setCode(Integer code) {
this.code = code;
}
}
3. 如何使用自定义的异常类
@RestController
public class LoginController {
@GetMapping("exception")
public String second(){
System.out.println("开始测试自定义的异常类");
throw new BusinessException("用户名密码错误",688);
}
}
执行完以后,控制台报错信息为
1. 使用 @RestControllerAdvice
使用 @RestControllerAdvice ,报错后异常被其捕获,postman调用请求显示为
Stutas是200
2. 不使用 @RestControllerAdvice
不使用 @RestControllerAdvice ,不存在异常捕获,postman调用请求显示为
Stutas是500
参考文章
【1】【规范】SpringBoot接口返回结果及异常统一处理,这样封装才优雅