一、单一校验处理
如果无法使用,则引用依赖
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>1.1.0.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.4.1.Final</version>
</dependency>
1)、给Bean添加校验注解
并定义自己的message提示
2)、开启校验功能@valid
效果:校验错误以后会有默认的响应
3)、给校验的bean后紧跟一个BindingResult result,就可以获取校验的结果
并定义自己的message提示
2)、开启校验功能@valid
效果:校验错误以后会有默认的响应
3)、给校验的bean后紧跟一个BindingResult result,就可以获取校验的结果
代码示例:
@RequestMapping("/save")
public R save(@Valid @RequestBody BrandEntity brand, BindingResult result){
if(result.hasErrors()){
Map<String,String> map=new HashMap<>();
result.getFieldErrors().forEach((item)->{
//fileError获取到错误提示
String message=item.getDefaultMessage();
//获取错误的属性的名字
String field=item.getField();
map.put(field,message);
});
return R.error(400,"提交的数据不合法").put("data",map);
}else{
brandService.save(brand);
}
return R.ok();
}
二、统一校验异常处理
不使用BindResult 就会抛出异常,统一被拦截
拦截处理:
创建一个异常处理类
代码示例:
/**
* 集中处理所以异常
*/
//路径下的数据校验议场全部都到此处处理
//统一异常处理
//@ResponseBody
//@ControllerAdvice(basePackages = "com.atguigu.gulimall.product.controller")
//注:RestControllerAdvice=@ResponseBody+@ControllerAdvice
@RestControllerAdvice(basePackages = "com.atguigu.gulimall.product.controller")
public class GulimallExceptionControllerAdvice {
//处理什么异常,先找到要处理的异常是哪个
@ExceptionHandler(value= MethodArgumentNotValidException.class)
public R handleVaildException(MethodArgumentNotValidException e){
BindingResult bindingResult=e.getBindingResult();
Map<String,String> errorMap=new HashMap<>();
bindingResult.getFieldErrors().forEach((fieldError -> {
errorMap.put(fieldError.getField(),fieldError.getDefaultMessage());
}));
return R.error(400,"数据校验出现问题").put("data",errorMap);
}
}
三、默认异常处理
代码示例:
@ExceptionHandler(value = Throwable.class)//异常的范围更大
public R handleException(Throwable throwable){
log.error("未知异常{},异常类型{}",
throwable.getMessage(),
throwable.getClass());
return R.error(BizCodeEnum.UNKNOW_EXEPTION.getCode(),
BizCodeEnum.UNKNOW_EXEPTION.getMsg());
}
错误状态码:
上面代码中,针对于错误状态码,是我们进行随意定义的,然而正规开发过程中,错误状态码有着严格的定义规则,如该在项目中我们的错误状态码定义
上面的用法主要是通过@Controller+@ExceptionHandler来进行异常拦截处理
BizCodeEnum
为了定义这些错误状态码,我们可以单独定义一个常量类,用来存储这些错误状态码
package com.atguigu.common.exception;
/***
* 错误码和错误信息定义类
* 1. 错误码定义规则为5为数字
* 2. 前两位表示业务场景,最后三位表示错误码。例如:100001。10:通用 001:系统未知异常
* 3. 维护错误码后需要维护错误描述,将他们定义为枚举形式
* 错误码列表:
* 10: 通用
* 001:参数格式校验
* 11: 商品
* 12: 订单
* 13: 购物车
* 14: 物流
*/
public enum BizCodeEnum {
UNKNOW_EXEPTION(10000,"系统未知异常"),
VALID_EXCEPTION( 10001,"参数格式校验失败");
private int code;
private String msg;
BizCodeEnum(int code, String msg) {
this.code = code;
this.msg = msg;
}
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
四、分组校验
如果新增和修改两个接口需要验证的字段不同,比如id字段,新增可以不传递,但是修改必须传递id,我们又不可能写两个vo来满足不同的校验规则。所以就需要用到分组校验来实现。
步骤:
创建分组接口Insert.class Update.class
在VO的属性中标注@NotBlank等注解,并指定要使用的分组,如@NotNull(message = "用户姓名不能为空",groups = {Insert.class,Update.class})
controller的方法上或者方法参数上写要处理的分组的接口信息,如@Validated(AddGroup.class)
业务方法参数上使用@Validated注解
@Validated的value值指定要使用的一个或多个分组
wu
五、分组校验的默认校验
validated
这里要是指定了分组,实体类上的注解就是指定了分组的注解才生效,
没有指定分组的默认不生效,要是没有指定分组,就是对没有指定分组的注解生效,指定分组的注解就不生效了,只能在valid生效
可以在自定义的异常分组接口中继承Default
类。所有没有写明group的都属于Default分组。
例:
默认没有指定分组的校验注解@NotBlank.在分组校验情况groups={AddGroup.class})下不生效
此外还可以在实体类上标注@GroupSequece({A.class,B.class})指定校验顺序
通过@GroupSequence指定验证顺序:先验证A分组,如果有错误立即返回而不会验证B分组,接着如果A分组验证通过了,那么才去验证B分组,最后指定User.class表示那些没有分组的在最后。这样我们就可以实现按顺序验证分组了。
关于Default,此处我springvalidation默认生成的验证接口,验证的范围是所有带有验证信息的属性,
若是属性上方写了验证组,则是验证该组内的属性
若是验证实体类类上写了GroupSequence({}) 则说明重写了Default验证接口,Default就按照GroupSequence里所写的组信息进行验证
六、自定义校验注解
Hibernate Validator提供了一系列内置的校验注解,可以满足大部分的校验需求。但是,仍然有一部分校验需要特殊定制,例如某个字段的校验,我们提供两种校验强度,当为normal强度时我们除了<>号之外,都允许出现。当为strong强度时,我们只允许出现常用汉字,数字,字母。内置的注解对此则无能为力,我们试着通过自定义校验来解决这个问题。
场景:要校验showStatus的0/1状态,可以用正则,但我们可以利用其他方式解决复杂场景。比如我们想要下面的场景
/**
* 显示状态[0-不显示;1-显示]
*/
@NotNull(groups = {AddGroup.class, UpdateStatusGroup.class})
@ListValue(vals = {0,1}, groups = {AddGroup.class, UpdateGroup.class, UpdateStatusGroup.class})
private Integer showStatus;
<!--校验-->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.1.0.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.4.1.Final</version>
</dependency>
<!--高版本需要javax.el-->
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.el</artifactId>
<version>3.0.1-b08</version>
</dependency>
1、自定义校验注解
必须有3个属性
- message()错误信息
- groups()分组校验
- payload()自定义负载信息
// 自定义注解 @Documented @Constraint(validatedBy = { ListValueConstraintValidator.class}) // 校验器 @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE }) // 哪都可以标注 @Retention(RUNTIME) public @interface ListValue { // 使用该属性去Validation.properties中取 String message() default "{com.atguigu.common.valid.ListValue.message}"; Class<?>[] groups() default { }; Class<? extends Payload>[] payload() default { }; // 数组,需要用户自己指定 int[] value() default {}; }
因为上面的message值对应的最终字符串需要去ValidationMessages.properties中获得,所以我们在common中新建文件
ValidationMessages.properties
文件内容
com.atguigu.common.valid.ListValue.message=必须提交指定的值 [0,1]
2、自定义校验器ConstraintValidator
上面只是定义了异常消息,但是怎么验证是否异常还没说,下面的ConstraintValidator就是说的
比如我们要限定某个属性值必须在一个给定的集合里,那么就通过重写initialize()方法,指定可以有哪些元素。
而controller接收到的数据用isValid(验证
public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> { //<注解,校验值类型> // 存储所有可能的值 private Set<Integer> set=new HashSet<>(); @Override // 初始化,你可以获取注解上的内容并进行处理 public void initialize(ListValue constraintAnnotation) { // 获取后端写好的限制 // 这个value就是ListValue里的value,我们写的注解是@ListValue(value={0,1}) int[] value = constraintAnnotation.value(); for (int i : value) { set.add(i); } } @Override // 覆写验证逻辑 public boolean isValid(Integer value, ConstraintValidatorContext context) { // 看是否在限制的值里 return set.contains(value); } }
具体的校验类需要实现ConstraintValidator接口,第一个泛型参数是所对应的校验注解类型,第二个是校验对象类型。在初始化方法initialize中,我们可以先做一些别的初始化工作,例如这里我们获取到注解上的value并保存下来,然后生成set对象。
真正的验证逻辑由isValid完成,如果传入形参的属性值在这个set里就返回true,否则返回false
3、关联校验器和校验注解
@Constraint(validatedBy = { ListValueConstraintValidator.class})
一个校验注解可以匹配多个校验器
4、使用实例
/**
* 显示状态[0-不显示;1-显示]
用value[]指定可以写的值
*/
@ListValue(value = {0,1},groups ={AddGroup.class})
private Integer showStatus;