SpringBoot 的全局异常处理 & 优雅的参数校验
一、全局处理异常
SpringBoot中有一个 @RestControllerAdvice 的注解,使用该注解表示开启了全局异常的捕获,我们只需在自定义一个方法使用 @ExceptionHandler
注解然后定义捕获异常的类型即可对这些捕获的异常进行统一的处理。
import com.macro.mall.common.api.CommonResult;
import com.macro.mall.common.exception.BusinessException;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class AdviceController {
// 响应码的定义
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(value = Exception.class)
public CommonResult exceptionHandler(Exception ex) {
if (ex instanceof BusinessException) {
return CommonResult.failed("请求有误, 请稍后再试! " + ex.getMessage());
}
return CommonResult.failed();
}
}
二、优雅的参数校验
spring-boot-starter-web 包里面有 hibernate-validator 包,不需要引用hibernate validator依赖。在 pom.xml 中添加上 spring-boot-starter-web 的依赖即可。
官网: http://hibernate.org/validator/documentation/
JSR 303 是Bean验证的规范 ,Hibernate Validator 是该规范的参考实现,它除了实现规范要求的注解外,还额外实现了一些注解。
@Null | 被注释的元素必须为 null |
@NotNull | 限制必须不为null |
@NotEmpty | 验证注解的元素值不为 null 且不为空(字符串长度不为0、集合大小不为0) |
@NotBlank | 验证注解的元素值不为空(不为null、去除首位空格后长度为0),不同于@NotEmpty,@NotBlank只应用于字符串且在比较时会去除字符串的空格 |
@Pattern(value) | 限制必须符合指定的正则表达式 |
@Size(max,min) | 限制字符长度必须在 min 到 max 之间(也可以用在集合上) |
验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email格式 | |
@Max(value) | 限制必须为一个不大于指定值的数字 |
@Min(value) | 限制必须为一个不小于指定值的数字 |
@DecimalMax(value) | 限制必须为一个不大于指定值的数字 |
@DecimalMin(value) | 限制必须为一个不小于指定值的数字 |
@AssertFalse | 限制必须为false (很少用) |
@AssertTrue | 限制必须为true (很少用) |
@Past | 限制必须是一个过去的日期 |
@Future | 限制必须是一个将来的日期 |
@Digits(integer,fraction) | 限制必须为一个小数,且整数部分的位数不能超过 integer,小数部分的位数不能超过 fraction (很少用) |
@Length | 被注释的字符串的大小必须在指定的范围内 |
@Size | 被注释的list、map在指定的大小范围 |
@Range | 被注释的元素必须在合适的范围内 |
@URL | 被注释的元素必须是有效URL |
@Negative | 负数 |
@NegativeOrZero | 0或者负数 |
@Positive | 整数 |
@PositiveOrZero | 0或者整数 |
@Valid | 递归的对关联的对象进行校验(对象内部包含另一个对象作为属性,属性上加@Valid,可以验证作为属性的对象内部的验证) |
例子:
(1)定义接口的入参类
@Data
public class RegisterParam {
/**
* 手机号
*/
@NotBlank(message = "手机号码不允许为空")
@Length(max = 11, min = 1, message = "手机号码必须是11个字符之间")
@Pattern(regexp = "^1[3|4|5|8][0-9]\\d{8}$", message = "电话号码格式不正确")
private String phone;
/**
* 验证码
*/
@NotBlank(message = "动态校验码不允许为空")
@Length(max = 6, min = 6, message = "动态校验码必须是6个字符之间")
private String otpCode;
@NotBlank(message = "用户名不允许为空")
@Length(max = 20, min = 4, message = "用户名长度必须在4-20字符之间")
private String username;
@NotBlank(message = "密码不允许为空")
@Length(max = 20, min = 8, message = "密码长度必须在8-20字符之间")
private String password;
}
(2)Controller中的接口定义,增加 @Valid 注解
@PostMapping("/register")
public CommonResult register(@Valid @RequestBody Register register) throws BusinessException {
int result = memberService.register(register);
if (result > 0) {
return CommonResult.success(null);
}
return CommonResult.failed();
}
(3)全局异常处理,处理 MethodArgumentNotValidException 的异常;
@RestControllerAdvice
public class AdviceController {
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(value = Exception.class)
public CommonResult exceptionHandler(Exception ex) {
if (ex instanceof BusinessException) {
return CommonResult.failed("请求有误, 请稍后再试: " + ex.getMessage());
}
else if (ex instanceof MethodArgumentNotValidException) {
MethodArgumentNotValidException exception = (MethodArgumentNotValidException) ex;
List<ObjectError> allErrors = exception.getBindingResult().getAllErrors();
StringBuilder msgBuilder = new StringBuilder();
for (ObjectError allError : allErrors) {
msgBuilder.append(allError.getDefaultMessage()).append("; ");
}
return CommonResult.failed("参数校验错误:" + msgBuilder.toString());
}
return CommonResult.failed();
}
}
三、参数分组校验
在实际开发中经常会遇到这种情况:想要用一个实体类去接收多个controller的参数,但是不同controller所需要的参数又有些许不同,而你又不想为这点不同去建个新的类接收参数。
比如新增用户信息的时候,不需要验证userId(因为系统生成),修改的时候需要验证userId;这种时候就可以使用参数分组来实现。
(1)定义两个空的接口: GroupA 和 GroupB
interface GroupA { } interface GroupB { }
(2)定义接口的入参类,增加 groups 属性;
@Data
public class Register {
@NotBlank(message = "手机号码不允许为空")
@Length(max = 11, min = 1, message = "手机号码必须是11个字符之间")
@Pattern(regexp = "^1[3|4|5|8][0-9]\\d{8}$", message = "电话号码格式不正确")
private String phone;
@NotBlank(message = "动态校验码不允许为空")
@Length(max = 6, min = 6, message = "动态校验码必须是6个字符之间")
private String otpCode;
@NotBlank(message = "用户名不允许为空", groups = GroupA.class)
@Length(max = 20, min = 4, message = "用户名长度必须在4-20字符之间")
private String username;
@NotBlank(message = "密码不允许为空")
@Length(max = 20, min = 8, message = "密码长度必须在8-20字符之间")
private String password;
}
(3)controller 中的接口定义
@PostMapping("/register")
public CommonResult register(@Validated({GroupA.class}) @RequestBody Register register) throws BusinessException {
int result = memberService.register(register);
if (result > 0) {
return CommonResult.success(null);
}
return CommonResult.failed();
}
结果:只会校验用户名不允许为空;
参考:
(1)SpringBoot 参数校验的方法: https://www.cnblogs.com/mooba/p/11276062.html
(2)@Validated和@Valid区别:https://blog.csdn.net/qq_27680317/article/details/79970590
(3)@Valid和@Validated的总结区分:https://www.jianshu.com/p/1dff31a1649d