springboot项目中如何使用全局异常处理和自定义业务异常
文章目录
一. 统一捕获异常
统一捕获异常,使用AOP的思想,解决在controller中大量try-catch重复代码。
@RestControllerAdvice
: 贴在类上,@RestControllerAdvice
的增强,同时可以在controller执行前后做一些额外逻辑。@ExceptionHandler(异常类.class)
:贴在方法上,可捕获指定类型的异常。
二. 使用枚举封装返回的异常信息
package io.coderyeah.ymcc.enums;
/**
* @author lqs
* @date 2022/10/19 11:12
*/
// 系统错误异常
public enum YmccSystemError {
SYSTEM_ERROR("10010", "系统错误,正在加班修理中-_-"),
SYSTEM_OAUTH_ERROR("10020", "你没有权限访问!未授权!"),
SYSTEM_PARAM_ERROR("10030", "数据格式错误");
// 错误码
private String code;
// 错误信息
private String message;
// 构造方法
YmccSystemError(String code, String message) {
this.code = code;
this.message = message;
}
public String getCode() {
return code;
}
public String getMessage() {
return message;
}
}
三. 自定义业务异常
package io.coderyeah.ymcc.exception;
import io.coderyeah.ymcc.enums.YmccSystemError;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @author lqs
* @date 2022/10/19 11:03
*/
// 自定义业务异常
@EqualsAndHashCode(callSuper = true)
@Data
public class BusinessException extends RuntimeException {
private String message;
private String code;
private YmccSystemError systemError;
public BusinessException(YmccSystemError systemError) {
this.systemError = systemError;
}
public BusinessException(String message, String code) {
this.message = message;
this.code = code;
}
public BusinessException(String message) {
this.message = message;
}
}
全局异常代码:包含三种全局异常处理
package io.coderyeah.ymcc.exception;
import io.coderyeah.ymcc.enums.YmccSystemError;
import io.coderyeah.ymcc.result.JSONResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
/**
* @author lqs
* @date 2022/10/19 11:06
*/
@RestControllerAdvice // controller全局异常处理
@Slf4j
public class GlobalExceptionHandler {
// 定义需要捕获的业务异常 主动抛出的异常
@ExceptionHandler(BusinessException.class)
public JSONResult businessException(HttpServletResponse response, BusinessException ex) {
log.warn("业务异常:{}", ex.getSystemError().getMessage() + ":" + ex.getSystemError().getCode());
log.warn("错误代码:{}", response.getStatus());
return JSONResult.error(ex.getSystemError());
}
// 定义需要捕获的系统异常 未知情况下发生的异常
@ExceptionHandler(Exception.class)
public JSONResult globalException(HttpServletResponse response, Exception ex) {
ex.printStackTrace();
log.warn("全局系统异常:{}", ex.getMessage());
log.warn("错误代码:{}", response.getStatus());
return JSONResult.error(YmccSystemError.SYSTEM_ERROR);
}
// 定义需要捕获的参数异常
@ExceptionHandler(MethodArgumentNotValidException.class)
public JSONResult parameterException(HttpServletResponse response, MethodArgumentNotValidException ex) {
ex.printStackTrace();
final StringBuffer paramInfo = new StringBuffer("数据格式错误:");
final List<ObjectError> errors = ex.getBindingResult().getAllErrors();
errors.forEach(err -> {
paramInfo.append(err.getDefaultMessage()).append(";");
});
// 去除末尾分号
paramInfo.deleteCharAt(paramInfo.lastIndexOf(";"));
log.warn("参数异常:{}", paramInfo.toString());
log.warn("错误代码:{}", response.getStatus());
return JSONResult.error(paramInfo.toString(), YmccSystemError.SYSTEM_PARAM_ERROR.getCode());
}
}
统一返回结果类
package io.coderyeah.ymcc.result;
import io.coderyeah.ymcc.enums.YmccSystemError;
import io.coderyeah.ymcc.exception.BusinessException;
import lombok.Builder;
import lombok.Data;
//返回JSON结果
@Data
//建造者模式
//@Builder
public class JSONResult {
private boolean success = true;
private String message = "成功";
//错误码,用来描述错误类型 ,1000 表示么有错误
private String code = "1000";
//返回的数据
private Object data;
/**
* 创建当前实例
**/
public static JSONResult success() {
return new JSONResult();
}
/**
* 创建当前实例
**/
public static JSONResult success(Object obj) {
JSONResult instance = new JSONResult();
instance.setData(obj);
return instance;
}
public static JSONResult success(Object obj, String code) {
JSONResult instance = new JSONResult();
instance.setCode(code);
instance.setData(obj);
return instance;
}
/**
* 创建当前实例
**/
public static JSONResult error(String message, String code) {
JSONResult instance = new JSONResult();
instance.setMessage(message);
instance.setSuccess(false);
instance.setCode(code);
return instance;
}
public static JSONResult error() {
JSONResult jsonResult = new JSONResult();
jsonResult.setSuccess(false);
return jsonResult;
}
/**
* 创建当前实例
**/
public static JSONResult error(String message) {
return error(message, null);
}
public static JSONResult error(YmccSystemError ex) {
return error(ex.getMessage(), ex.getCode());
}
public static JSONResult error(BusinessException ex) {
JSONResult jsonResult = new JSONResult();
jsonResult.setSuccess(false);
jsonResult.setMessage(ex.getMessage());
return jsonResult;
}
}
四.JSR303校验
-
导入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> <version>2.5.10</version> </dependency>
-
Bean Validation 中内置的 constraint
相关注解
Constraint | 详细信息 |
---|---|
@Null | 被注释的元素必须为 null |
@NotNull | 被注释的元素必须不为 null |
@AssertTrue | 被注释的元素必须为 true |
@AssertFalse | 被注释的元素必须为 false |
@Min(value) | 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 |
@Max(value) | 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 |
@DecimalMin(value) | 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 |
@DecimalMax(value) | 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 |
@Size(max, min) | 被注释的元素的大小必须在指定的范围内 |
@Digits (integer, fraction) | 被注释的元素必须是一个数字,其值必须在可接受的范围内 |
@Past | 被注释的元素必须是一个过去的日期 |
@Future | 被注释的元素必须是一个将来的日期 |
@Pattern(value) | 被注释的元素必须符合指定的正则表达式 |
Constraint | 详细信息 |
---|---|
@Email | 被注释的元素必须是电子邮箱地址 |
@Length | 被注释的字符串的大小必须在指定的范围内 |
@NotEmpty | 被注释的字符串的必须非空 |
@Range | 被注释的元素必须在合适的范围内 |
@Pattern(regexp = "^1(3\\d|4[5-9]|5[0-35-9]|6[567]|7[0-8]|8\\d|9[0-35-9])\\d{8}$", message = "手机号格式错误")
private String tel;
@Email(message = "邮箱格式错误")
private String email;
-
开启校验
@Valid 或者 @Validated都可以标识该类需要进行校验,在类上也可以加该注解。
@RequestMapping(value = "/save", method = RequestMethod.POST) public JSONResult saveOrUpdate(@Valid @RequestBody User user) { if (user.getId() != null) { userService.updateById(user); } else { user.insert(user); } return JSONResult.success(); }
五.自定义校验注解
-
定义校验注解
package io.coderyeah.ymcc.anno; import io.coderyeah.ymcc.validator.PhoneValidator; import javax.validation.Constraint; import javax.validation.Payload; import java.lang.annotation.*; /** * @author lqs * @date 2022/10/19 */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented @Constraint(validatedBy = PhoneValidator.class)//此处指定了注解的实现类为PhoneValidator public @interface Phone {// 用来校验手机号码的注解 String message() default "无效的手机格式"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
-
定义校验器
package io.coderyeah.ymcc.validator; import io.coderyeah.ymcc.anno.Phone; import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * @author lqs * @date 2022/10/19 */ public class PhoneValidator implements ConstraintValidator<Phone, String> { private static final String PHONE_REGEX = "^((13[0-9])|(14[5|7])|(15([0-3]|[5-9]))|(17[013678])|(18[0-9]))\\d{8}$"; @Override public void initialize(Phone constraintAnnotation) { //初始化 } @Override public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) { //对值进行手机号正则判断 Pattern p = Pattern.compile(PHONE_REGEX); Matcher m = p.matcher(value); return m.matches(); } }
-
使用校验注解
// 校验手机号 @Phone private String tel;