JSR303
作用
往往我们会在前端进行一些表单校验等等,来确保传递的值是合法的。但是有一些手段可以绕过前端表单校验,所以我们需要在后端对javaBean进行校验。这就是用的JSR303。
大概步骤
1)、给相关的实体类上添加校验注解。比如我们这次要校验brand,那么就要给brandEntity的属性增加校验注解
BrandEntity中有这么一个属性,那我们就可以用@NotBlank
来校验。
/**
* 品牌名
*/
@NotBlank(message = "品牌名必须要提交"}) //提交的数据不能为空,空格都不行。
private String name;
2)、需要给要调用校验的方法开启 校验功能,也就是在方法括号内添加@Valid注解。
比如在BrandController
中会有一个方法,要调用BrandEntity来对其进行校验,那么我们就要去该方法开启校验功能。
3)、给校验后的bean紧跟一个BindingResult,就可以用它来接受校验结果
// 我不写BindingResult,那么异常就会被抛出去
if (result.hasErrors()) {//如果有错误
Map<String, String> map = new HashMap<>();
// BindingResult 必须紧跟在要校验的对象后,用来接收校验结果
List<FieldError> fieldErrors = result.getFieldErrors();
for (FieldError error : fieldErrors) {
//返回的错误提示,如果我们自己在注解上定义了就会返回自己定义的信息
String message = error.getDefaultMessage();
// 获取错误属性的名字
String field = error.getField();
map.put(field, message);//把信息封装到map中
}
return R.error(400, "提交的数据不合法").put("data", map);
} else {
//没错误再去执行逻辑
brandService.save(brand);
}
4)、可以使用分组校验(多场景复杂校验):
给校验注解加上
groups
属性来指明它在那个组。@NotEmpty(groups = {AddGroup.class})需要自己建立一个空接口用来作为这个 组。因为本次拆分了服务,所以把这个组建立在
common
服务下。
在校验方法上用
@Validated({AddGroup.class})
,不用原来的开启校验的注解了。 代表着我这个方法就校验分组为AddGroup组的属性。没有指定gropus的属性在@Validated指定了分组的情况下无效,在@Validated没有指定分组的情况下有效。 也即@Validated指定了分组就只管校验属性也指定了同样分组的。如果@Validated没有指定分组,就管校验属性也没分组的
5)、自定义校验:
有些时候第三方提供的校验注解不能满足我们的业务需求,我们就需要自己来自定义校验注解。同上,这次还是写在common
服务中。
假设这次想实现校验显示状态这个属性只能为0或者1
/**
* 显示状态[0-不显示;1-显示]
*/
@ListValue(vals={0,1},groups = {AddGroup.class, UpdateStatusGroup.class})//自定义的校验注解
private Integer showStatus;
- 编写一个自定义的校捡注解
@Documented
/*validatedBy:指定这个注解用那个校验器来进行校验
ListValueConstraintValidator.class:只能校验Integer类型的。
如果以后还想校验Double型的,可以再写一个校验器,然后在{xx,xx}里写上就行了
*/
@Constraint(validatedBy = { ListValueConstraintValidator.class})
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
public @interface ListValue {
// 默认去获取ValidationMessages.properties配置文件下的com.sl.common.valid.ListValue.message属性的值,来当作错误信息
//这里我们自己建立一个这个同名的配置文件再写上属性就好。
String message() default "{com.sl.common.valid.ListValue.message}";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
int[] vals() default { };//定义一个int数组,用来存放哪些值是满足校验类型的。 也就是规定那些值可以通过校验
}
- 编写一个自定义的校验器
/**
* 自定义的校验注解器
*
* implements ConstraintValidator<ListValue,Integer>
* 两个泛型:第一个:校验注解,第二个:指定我们要校验什么类型的数据
*/
public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> {
private Set<Integer> set=new HashSet<>();
//初始化方法
@Override
public void initialize(ListValue constraintAnnotation) {
int[] vals=constraintAnnotation.vals();//拿到可以通过校验的值
for (int i = 0; i <vals.length ; i++) {
set.add(vals[i]);//把可以通过校验的值放到set中,方便isValid()的判断
}
}
/**
* 判断是否校验成功
* @param value 需要校验的值
* @param context
* @return
*/
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
return set.contains(value);//看看set集合中是否有value
}
}
- 关联自定义的校验器和自定义的校验注解
在自定义注解中:
@Constraint(validatedBy = { ListValueConstraintValidator.class})
因为以后要处理的异常很多,所以做一个统一的异常处理:@ControllerAdvice
//@ControllerAdvice
//@ResponseBody
/**
* 集中处理所有异常
*/
@Slf4j
@RestControllerAdvice(basePackages = "com.sl.gulimall.product.controller") //相当于上面两个的合体
public class GulimallException {
@ExceptionHandler(value = MethodArgumentNotValidException.class)//代表捕获MethodArgumentNotValidException异常
public R handleVaildException(MethodArgumentNotValidException e) {
log.error("数据出现问题{},异常类型{}", e.getMessage(), e.getClass());
BindingResult result = e.getBindingResult();//拿到异常信息
Map<String, String> errorMap = new HashMap<>();
List<FieldError> fieldErrors = result.getFieldErrors();
for (FieldError error : fieldErrors) {
//把出现异常的属性,和异常信息放入map中
errorMap.put(error.getField(),error.getDefaultMessage());
}
//在gulimall-common下统一了异常的种类
return R.error(baseCode.VAILD_EXCEPTION.getCode(),baseCode.VAILD_EXCEPTION.getMsg()).put("data",errorMap);
}
@ExceptionHandler(value = Throwable.class)//代表捕获所有异常
public R handleException(MethodArgumentNotValidException e) {
return R.error(baseCode.UNNKNOW_EXCEPTION.getCode(),baseCode.UNNKNOW_EXCEPTION.getMsg());
}
}
注意:用了统一的异常处理那么就不要用BindingResult
了,这样才会把异常抛出去,进而才能被我们自己写的处理异常的类捕获到。