1、简介
jsr 是 Java Specification Requests 的缩写,意思是java的请求规范。周志明老师的书上还着重介绍过jsr292(jvm多语言支持包括Kotlin,Clojure,JRuby,Scala等)。
JSR303着重参数校验功能,点开javax.validation.constraints,可以看到已经封装好的注解有这些:
使用jsr303规范很简单,第一步在实体类相应字段上标注校验注解,比如@Email或者标注自定义校验注解@Pattern(regexp=”“)自定义正则表达式来处理;第二步是使用校验,只需要在@RequestBody之前加上@Valid注解即表明开启校验。
2、分组校验
更复杂的场景,我们可以分组校验:
1)、 @NotBlank(message = "品牌名必须提交",groups = {AddGroup.class,UpdateGroup.class}) 给校验注解标注什么情况需要进行校验,比如增加不校验修改时校验。这里group里传入的是个接口。 2)、开启分组校验要使用spring实现的注解@Validated({AddGroup.class}) 3)、默认没有指定分组的校验注解比如@NotBlank,在分组校验情况@Validated({AddGroup.class})下不生效,只会在@Validated生效,也就是说Validated后加了分组那么不加分组的校验注解就会失效;
细节如下
@Data
@TableName("pms_brand")
public class BrandEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 品牌id
*/
@NotNull(message = "修改必须指定品牌id", groups = {UpdateGroup.class})
@Null(message = "新增不能指定id", groups = {AddGroup.class})
@TableId
private Long brandId;
/**
* 品牌名
*/
@NotBlank(message = "品牌名必须提交", groups = {AddGroup.class, UpdateGroup.class})
private String name;
/**
* 品牌logo地址
*/
@NotBlank(groups = {AddGroup.class})
@URL(message = "logo必须是一个合法的url地址", groups = {AddGroup.class, UpdateGroup.class})
private String logo;
/**
* 介绍
*/
private String descript;
/**
* 显示状态[0-不显示;1-显示]
*/
// @Pattern()
@NotNull(groups = {AddGroup.class, UpdateStatusGroup.class})
@ListValue(values = {0, 1}, groups = {AddGroup.class, UpdateStatusGroup.class})
private Integer showStatus;
/**
* 检索首字母
*/
@NotEmpty(groups = {AddGroup.class})
@Pattern(regexp = "^[a-zA-Z]$", message = "检索首字母必须是一个字母", groups = {AddGroup.class, UpdateGroup.class})
private String firstLetter;
/**
* 排序
*/
@NotNull(groups = {AddGroup.class})
@Min(value = 0, message = "排序必须大于等于0", groups = {AddGroup.class, UpdateGroup.class})
private Integer sort;
}
controller层内容:
public R update(@Validated(UpdateGroup.class) @RequestBody BrandEntity brand)
3、自定义校验注解
还有自定义校验,可以编写一个自定义的校验注解,然后编写一个自定义的校验器 ConstraintValidator,然后两者关联。
也就是说@Pattern()正则不能满足校验的情况,可以使用自定义校验注解。
比如对showStatus做自定义校验,规定只能是整数0或1。
需要先按照规范自定义一个注解@ListValue()
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Documented
@Constraint(validatedBy = {ListValueConstraintValidator.class})
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
public @interface ListValue {
String message() default "{com.flitsneak.common.valid.ListValue.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
int[] values() default {};
}
注解增加属性int[],提示信息仿照规范自定义common模块resource目录下新建
ValidationMessages.properties文件
com.flitsneak.common.valid.ListValue.message=必须提交指定的值
validatedBy后传入我们自定义的校验器,注解作为参数通过自定义校验器ListValueConstraintValidator对参数进行校验。
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.util.HashSet;
import java.util.Set;
/**
* @Author FlitSneak
* @Date 2021/6/24
*/
public class ListValueConstraintValidator implements ConstraintValidator<ListValue,Integer> {
private final Set<Integer> set = new HashSet<>();
/**
* 初始化方法
* 参数:自定义注解的详细信息
*/
@Override
public void initialize(ListValue constraintAnnotation) {
int[] values = constraintAnnotation.values();
for (int val : values) {
set.add(val);
}
}
/**
* 判断是否校验成功
*
* @param value 需要校验的值
* @param context
* @return
*/
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
return set.contains(value);
}
}
就是将注解的值放大set集合之中,然后对字段之判断是否在set集合之中。
统一拦截处理
校验响应的结果可以用BindingResult来接收处理返回,但是相当麻烦,推荐做统一拦截处理。
BingdingResult处理方式:
public R save(@Valid @RequestBody BrandEntity brand, BindingResult bindingResult){
if (bindingResult.hasErrors()){
Map<String,String> map = new HashMap<>();
//获取校验错误结果
bindingResult.getFieldErrors().forEach(i->{
//获取到错误提示
String message = i.getField();
//获取出错的字段
String field = i.getField();
map.put(field,message);
});
return R.error(400,"校验错误").put("data",map);
}else {
brandService.save(brand);
return R.ok();}
}
改为ControllerAdvice统一拦截处理,
@Slf4j
@RestControllerAdvice(basePackages = "com.flitsneak.mall.product.controller")
public class MallControllerAdvice {
@ExceptionHandler(value= MethodArgumentNotValidException.class)
public R handleValidException(MethodArgumentNotValidException e){
log.error("数据校验出现问题{},异常类型:{}",e.getMessage(),e.getClass());
BindingResult bindingResult = e.getBindingResult();
Map<String,String> errorMap = new HashMap<>();
bindingResult.getFieldErrors().forEach((fieldError)->{
errorMap.put(fieldError.getField(),fieldError.getDefaultMessage());
});
return R.error(BizCodeEnum.VALID_EXCEPTION.getCode(),BizCodeEnum.VALID_EXCEPTION.getMsg()).put("data",errorMap);
}
@ExceptionHandler(value = Throwable.class)
public R handleException(Throwable throwable){
log.error("错误:",throwable);
return R.error(BizCodeEnum.UNKNOWN_EXCEPTION.getCode(),BizCodeEnum.UNKNOWN_EXCEPTION.getMsg());
}
}
指定拦截的是MethodArgumentNotValidException异常,异常对象e里面可以获取BindingResult,处理方式一样。
补充
前端和后端都应该对参数做检验屏蔽非法请求,jsr303很多企业并没有应用,仍然是 使用CollectionUtil或者StringUtil进行处理。
我们项目用的springboot版本是2.3.x,而2.3.x以上剥离了jsr303,所以要使用注解校验,需要导入一下两个包:
<!-- https://mvnrepository.com/artifact/javax.validation/validation-api -->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-validation -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<version>2.3.4.RELEASE</version>
</dependency>
spring-boot-starter-validation下的version字段和springboot版本对应,本项目springboot版本是2.3.4.RELEASE
标签:groups,自定义,JSR303,校验,AddGroup,注解,class,统一 From: https://blog.csdn.net/nonagontech/article/details/144149688