SpringBoot教程(二十七) | SpringBoot集成AOP实现异常处理
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接口返回结果及异常统一处理,这样封装才优雅