一、简单使用
1.1 准备
JSR303检验协议,javax.validation.constraints包下注解实现。
导包:
javax.validation
validation-api
1.1.0.Final
1.2 实现
- 给bean添加校验注解:javax.validation.constraints,并定义自己的message提示。
@NotBlank(message = "若不满足返回的错误描述")
private String name;
- 控制层开启校验功能,若不加@Validated,则实体类上的校验注解无效。
@RequestMapping("/save")
public R save(@Validated @RequestBody BrandEntity brand){
//业务处理
return R.ok();
}
1.3 注解扩展
@NotNull(message = "字段值不能为空,Map 和 Array 对象不能是 null, 但可以是空集(size = 0)")
@NotEmpty(message = "Map 和 Array 对象不能是 null 并且相关对象的 size 大于 0")
@NotBlank(message = "String 不是 null 且去除两端空白字符后的长度(trimmed length)大于 0")
@Max(value = 20,message = "最大长度为20,相应的还有@Min(value)")
@Size(max=10,min=5,message = "字段长度要在5-10之间")
@Pattern(regexp = "正则表达式",message = "不满足正则表达式")
@AssertTrue(message = "字段为true才能通过,针对布尔类型")
@Future(message = "时间在当前时间之后才可以通过,相应的还有@Past")
@DecimalMin(value) 被注释的元素必须是一个数字,其值必须大于等于指定的最小值
@DecimalMax(value) 被注释的元素必须是一个数字,其值必须小于等于指定的最大值
@Digits(integer,fraction) 被注释的元素必须是一个数字,其值必须在可接受的范围内
@Email 被注释的元素必须是电子邮件地址
@Length 被注释的字符串的大小必须在指定的范围内
@Range 被注释的元素必须在合适的范围内
二、接受返回的错误信息
- 给校验的bean后紧跟一个BindingResult,就可以获取到校验的结果。
@RequestMapping("/save")
public R save(@Validated @RequestBody BrandEntity brand,BindingResult result){
//业务处理
return R.ok();
}
- 对错误结果进行处理
@RequestMapping("/save")
public R save(@Validated @RequestBody BrandEntity brand,BindingResult result){
if (result.hasErrors()){
Map errmap = new HashMap<>();
//.getFieldErrors()方法获取错误的校验结果
result.getFieldErrors().forEach((item)->{
//item为FieldError类对象,getDefaultMessage获取错误描述
String message = item.getDefaultMessage();
//获取错误的属性的名字
String field = item.getField();
errmap.put(field,message);
});
return R.error(400,"输入数据不合法").put("data",errmap);
}else {
//业务处理
}
return R.ok();
}
三、统一的异常处理
3.1 准备
- 导入依赖
org.springframework
spring-web
5.3.3
compile
- @ControllerAdvice注解
ControllerAdvice是Spring3.2提供的新注解,它是一个Controller增强器,可对controller中被 @RequestMapping注解的方法加一些逻辑处理。最常用的就是异常处理
3.2 使用
- 设置需要作用的包
@ControllerAdvice(basePackages = "com.fangk.mall.product.controller")
public class FangkmallExceptionContrpllerAdvice {
}
- 普通controller层方法不处理数据校验错误返回信息,直接向上抛出
@RequestMapping("/save")
public R save(@Validated @RequestBody BrandEntity brand/*,BindingResult result*/){
//只有业务处理了,简洁清晰
return R.ok();
}
- 书写controllerAdvice代码
@Slf4j
//@ResponseBody
//@ControllerAdvice(basePackages = "com.fangk.mall.product.controller")
@RestControllerAdvice(basePackages = "com.fangk.mall.product.controller")
public class FangkmallExceptionContrpllerAdvice {
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public R handleVaildException(MethodArgumentNotValidException e){
log.error("数据校验出现问题{},异常类型{}",e.getMessage(),e.getClass());
BindingResult bindingResult = e.getBindingResult();
Map errorMap = new HashMap<>();
bindingResult.getFieldErrors().forEach((item)->{
errorMap.put(item.getField(),item.getDefaultMessage());
});
return R.error(400,"数据校验出现问题").put("data",errorMap);
}
}
- Controller里面的方法抛出了MethodArgumentNotValidException异常就会执行上面的这个方法来进行异常处理。
- 该controller中可以有多个处理不同异常的方法,按照顺序匹配。兜底的可以选择Exception。
- ResponseBody和ControllerAdvice注解可以合并为RestControllerAdvice。
- 业务代码实现中,controller中可以根据场景向上抛出不同的异常供active进行处理。
四、枚举定时码值映射
public enum BizCodeEnume {
UNKNOW_EXCEPTION(10000,"系统未知异常"),
VAILD_EXCEPTION(10001,"参数格式校验失败");
private int code;
private String msg;
BizCodeEnume(int code,String msg){
this.code = code;
this.msg = msg; }
public int getCode() { return code; }
public String getMsg() { return msg; }
}
//这样使用
return R.error(BizCodeEnume.VAILD_EXCEPTION.getCode(),
BizCodeEnume.VAILD_EXCEPTION.getMsg()).put("data",errorMap);
五、分组校验
5.1 为什么要用
一个实体类bean可能会需要应对不同的接口请求,每个接口对于字段的格式校验会出现不同要求。比如id字段,新增时因为自增序列所以不需要传输,而修改时则必传。
为了不重复创建相同字段而校验注解不同的bean,所以javax.validation.constraints的格式校验注解中为我们提供了groups参数。
5.2 如何使用
- 校验注解中加入groups参数
@NotEmpty(groups={AddGroup.class})
@Pattern(regexp="^[a-zA-Z]$",message = "检索首字母必须是一个字母",groups={AddGroup.class,UpdateGroup.class})
private String firstLetter;
- AddGroup新增时,需要校验非空和正则。
- UpdateGroup更新时,只需校验正则。
- groups参数格式为Class[] groups() default { };,故定义个空接口即可。
- 业务层增加@Validated
@RequestMapping("/save")
public R save(@Validated({AddGroup.class}) @RequestBody BrandEntity brand/*,BindingResult result*/){
//只有业务处理了,简洁清晰
return R.ok();
}
- 默认没有指定分组校验的注解,在分组校验情况下不生效。
- Validated({AddGroup.class},只有校验注解中有AddGroup的才会生效,其他不生效。
六、自定义校验注解
6.1 编写一个自定义的校验注解
@Documented
//指定校验器,此处若不指定,则需在初始化时指定
@Constraint(validatedBy = { ListValueConstraintValidator.class })
//表明注解可以使用的地方
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
public @interface ListValue {
String message() default "{com.atguigu.common.valid.ListValue.message}";
Class[] groups() default { };
Class[] payload() default { };
int[] vals() default { };
}
6.2 编写一个自定义的校验器
public class ListValueConstraintValidator implements ConstraintValidator {
private Set set = new HashSet<>();
//初始化方法
@Override
public void initialize(ListValue constraintAnnotation) {
int[] vals = constraintAnnotation.vals();
for (int val : vals) {
set.add(val);
}
}
//判断是否校验成功
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
return set.contains(value);
}
}
6.3 关联自定义的校验注解和校验器
@Documented
@Constraint(validatedBy = { ListValueConstraintValidator.class【可以指定多个不同的校验器,适配不同类型校验】 })
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)