在 Spring Boot 中,可以通过使用 @ControllerAdvice
注解和 @ExceptionHandler
注解来实现全局异常拦截。
@RestControllerAdvice
@RestControllerAdvice
是 Spring Framework 提供的注解,用于定义全局异常处理类,并且结合 @ExceptionHandler
注解来处理异常。与 @ControllerAdvice
不同的是,@RestControllerAdvice
默认情况下会将返回值转换为 JSON 格式。
@RestControllerAdvice
public class GlobalExceptionHandler {
//.....拦截异常方法
}
@ResponseStatus(...)
@ResponseStatus(HttpStatus.BAD_REQUEST)
是一个注解,用于在异常处理方法上指定特定的HTTP状态码。当该异常被抛出时,将返回指定的HTTP状态码给客户端。
@RestControllerAdvice
public class GlobalExceptionHandler {
//.....拦截异常方法
/**
* 缺少请求体异常处理器
* @param e 缺少请求体异常 使用get方式请求 而实体使用@RequestBody修饰
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ResponseResult parameterBodyMissingExceptionHandler(HttpMessageNotReadableException e) {
String requestURI = httpServletRequest.getRequestURI();
log.error("请求地址'{}',请求体缺失'{}'", requestURI, e.getMessage());
return ResponseResult.errorResult(HttpCodeEnum.SYSTEM_ERROR.getCode(), sysError);
}
}
@ExceptionHandler(...)
@ExceptionHandler(...)
是一个异常处理注解,用于捕获请求的异常。当客户端发送的请求消息无法被框架正确解析时,将抛出该异常并调用对应的异常处理方法。
@RestControllerAdvice
public class GlobalExceptionHandler {
//.....拦截异常方法
/**
* 缺少请求体异常处理器
* @param e 缺少请求体异常 使用get方式请求 而实体使用@RequestBody修饰
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseResult parameterBodyMissingExceptionHandler(HttpMessageNotReadableException e) {
String requestURI = httpServletRequest.getRequestURI();
log.error("请求地址'{}',请求体缺失'{}'", requestURI, e.getMessage());
return ResponseResult.errorResult(HttpCodeEnum.SYSTEM_ERROR.getCode(), sysError);
}
}
RuntimeException
RuntimeException
是 Java 提供的一个运行时异常类。与受检查异常不同,运行时异常不需要在方法声明中显式地声明或捕获,并且在运行时抛出时也不需要强制捕获或处理。所以我们可以在全局异常捕获中去捕获这个异常
public class BusinessException extends RuntimeException {
private int code;
//使用枚举构造
public BusinessException(HttpCodeEnum httpCodeEnum){
super(httpCodeEnum.getMsg());
this.code=httpCodeEnum.getCode();
}
//使用自定义消息体
public BusinessException(HttpCodeEnum httpCodeEnum, String msg){
super(msg);
this.code=httpCodeEnum.getCode();
}
//根据异常构造
public BusinessException(HttpCodeEnum httpCodeEnum, Throwable msg){
super(msg);
this.code=httpCodeEnum.getCode();
}
}
上述代码定义了一个名为 BusinessException
的自定义异常类,它继承自 RuntimeException
。
这个自定义异常类具有以下特点:
- 包含了一个
code
字段,用于表示异常的错误码。 - 提供了不同的构造方法,以方便在抛出异常时指定错误码和错误信息。
BusinessException(HttpCodeEnum httpCodeEnum)
构造方法使用枚举类型HttpCodeEnum
来设置异常的错误码和错误信息。BusinessException(HttpCodeEnum httpCodeEnum, String msg)
构造方法使用自定义的错误信息来设置异常的错误码和错误信息。BusinessException(HttpCodeEnum httpCodeEnum, Throwable msg)
构造方法使用其他异常的实例来设置异常的错误码,并可选地提供通过Throwable
获取的错误信息。
HttpCodeEnum
枚举类
我们还需要一个类表示 HTTP 响应的状态码和对应的消息 ,以下为基本的举例查考。
public enum HttpCodeEnum {
// 成功
SUCCESS(200, "操作成功"),
// 登录
NEED_LOGIN(401, "需要登录后操作"),
NO_OPERATOR_AUTH(403, "无权限操作"),
SYSTEM_ERROR(500, "出现错误"),
USERNAME_EXIST(501, "用户名已存在"),
PHONENUMBER_EXIST(502, "手机号已存在"), EMAIL_EXIST(503, "邮箱已存在"),
REQUIRE_USERNAME(504, "必需填写用户名"),
CONTENT_NOT_NULL(506, "评论内容不能为空"),
FILE_TYPE_ERROR(507, "文件类型错误"),
USERNAME_NOT_NULL(508, "用户名不能为空"),
NICKNAME_NOT_NULL(509, "昵称不能为空"),
PASSWORD_NOT_NULL(510, "密码不能为空"),
EMAIL_NOT_NULL(511, "邮箱不能为空"),
NICKNAME_EXIST(512, "昵称已存在"),
LOGIN_ERROR(505, "用户名或密码错误");
int code;
String msg;
HttpCodeEnum(int code, String errorMessage) {
this.code = code;
this.msg = errorMessage;
}
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
上述代码定义了一个 HttpCodeEnum
枚举类,用于表示 HTTP 响应的状态码和对应的消息。
这个枚举类具有以下特点:
- 包含一组枚举常量,每个常量代表一个 HTTP 响应状态。
- 每个常量都有一个整型的
code
和一个字符串类型的msg
,分别表示状态码和对应的消息。 - 提供了相应的构造方法、获取
code
和msg
的方法。
ResponseResult类
该类的主要作用是封装接口返回的数据,统一格式化输出,方便前端调用和展示。
import lombok.Data;
import java.io.Serializable;
@Data
public class ResponseResult<T> implements Serializable {
private Boolean success;
private Integer code;
private String msg;
private T data;
public ResponseResult() {
this.success=true;
this.code = HttpCodeEnum.SUCCESS.getCode();
this.msg = HttpCodeEnum.SUCCESS.getMsg();
}
public ResponseResult(Integer code, T data) {
this.code = code;
this.data = data;
}
public ResponseResult(Integer code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
}
public ResponseResult(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
public static ResponseResult errorResult(int code, String msg) {
ResponseResult result = new ResponseResult();
return result.error(code, msg);
}
public static ResponseResult okResult() {
ResponseResult result = new ResponseResult();
return result;
}
public static ResponseResult okResult(int code, String msg) {
ResponseResult result = new ResponseResult();
return result.ok(code, null, msg);
}
public static ResponseResult setHttpCodeEnum(HttpCodeEnum enums) {
return okResult(enums.getCode(), enums.getMsg());
}
public ResponseResult<?> error(Integer code, String msg) {
this.success=false;
this.code = code;
this.msg = msg;
return this;
}
public ResponseResult<?> ok(Integer code, T data) {
this.success=true;
this.code = code;
this.data = data;
return this;
}
public ResponseResult<?> ok(Integer code, T data, String msg) {
this.success=true;
this.code = code;
this.data = data;
this.msg = msg;
return this;
}
public ResponseResult<?> ok(T data) {
this.success=true;
this.data = data;
return this;
}
}
全局异常捕获
全局异常捕获是一种处理应用程序中未处理的异常的机制,它可以统一处理应用程序中的异常,避免异常导致程序崩溃或向用户显示不友好的错误信息。我们可以通过上述的解释去捕获异常,定义code类型枚举返回ResponseResult给前端
import com.example.demo.util.HttpCodeEnum;
import com.example.demo.util.ResponseResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.util.CollectionUtils;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
@RestControllerAdvice
public class GlobalExceptionHandler {
private static final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class);
@Autowired
private HttpServletRequest httpServletRequest;
private final String sysError="系统出错";
/**
* 缺少请求体异常处理器
* @param e 缺少请求体异常 使用get方式请求 而实体使用@RequestBody修饰
*/
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(HttpMessageNotReadableException.class)
public ResponseResult parameterBodyMissingExceptionHandler(HttpMessageNotReadableException e) {
String requestURI = httpServletRequest.getRequestURI();
log.error("请求地址'{}',请求体缺失'{}'", requestURI, e.getMessage());
return ResponseResult.errorResult(HttpCodeEnum.SYSTEM_ERROR.getCode(), sysError);
}
/*
* @Description: 捕获请求方法异常,比如post接口使用了get
*/
@ResponseStatus(HttpStatus.METHOD_NOT_ALLOWED)
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public ResponseResult methodNotAllowedHandler(HttpRequestMethodNotSupportedException e) {
String requestURI = httpServletRequest.getRequestURI();
log.error("请求地址'{}',请求方法不被允许'{}'", requestURI, e.getMessage());
return ResponseResult.errorResult(HttpCodeEnum.SYSTEM_ERROR.getCode(), sysError);
}
// get请求的对象参数校验异常
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler({MissingServletRequestParameterException.class})
public ResponseResult bindExceptionHandler(MissingServletRequestParameterException e) {
String requestURI = httpServletRequest.getRequestURI();
log.error("请求地址'{}',get方式请求参数'{}'必传", requestURI, e.getParameterName());
return ResponseResult.errorResult(HttpCodeEnum.SYSTEM_ERROR.getCode(), sysError);
}
// post请求的对象参数校验异常
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler({MethodArgumentNotValidException.class})
public ResponseResult methodArgumentNotValidHandler(MethodArgumentNotValidException e) {
String requestURI = httpServletRequest.getRequestURI();
log.error("请求地址'{}',post方式请求参数异常'{}'", requestURI, e.getMessage());
List<ObjectError> allErrors = e.getBindingResult().getAllErrors();
return ResponseResult.errorResult(HttpCodeEnum.SYSTEM_ERROR.getCode(), getValidExceptionMsg(allErrors));
}
// 业务类异常
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler(BusinessException.class)
public ResponseResult businessExceptionHandler(BusinessException e) {
String requestURI = httpServletRequest.getRequestURI();
System.out.println(e);
log.error("请求地址'{}',捕获业务类异常'{}'", requestURI,e.getMessage());
return ResponseResult.errorResult(HttpCodeEnum.SYSTEM_ERROR.getCode(), e.getMessage());
}
// 运行时异常
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler(RuntimeException.class)
public ResponseResult runtimeExceptionHandler(RuntimeException e) {
String requestURI = httpServletRequest.getRequestURI();
log.error("请求地址'{}',捕获运行时异常'{}'", requestURI, e.getMessage());
return ResponseResult.errorResult(HttpCodeEnum.SYSTEM_ERROR.getCode(), e.getMessage());
}
// 系统级别异常
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
@ExceptionHandler(Throwable.class)
public ResponseResult throwableExceptionHandler(Throwable e) {
String requestURI = httpServletRequest.getRequestURI();
log.error("请求地址'{}',捕获系统级别异常'{}'", requestURI,e.getMessage());
return ResponseResult.errorResult(HttpCodeEnum.SYSTEM_ERROR.getCode(), e.getMessage());
}
private String getValidExceptionMsg(List<ObjectError> errors) {
if(!CollectionUtils.isEmpty(errors)){
StringBuilder sb = new StringBuilder();
errors.forEach(error -> {
if (error instanceof FieldError) {
sb.append(((FieldError)error).getField()).append(":");
}
sb.append(error.getDefaultMessage()).append(";");
});
String msg = sb.toString();
msg = msg.substring(0, msg.length() -1);
return msg;
}
return null;
}
}
测试
入参不正确时
发出请求
返回结果
捕获异常
运行时错误
发起请求
返回结果
捕获异常
业务异常
发送请求
返回结果
捕获异常