1、什么时JSR303?
JSR是Java Specification Requests的缩写,意思是Java 规范提案。是指向JCP(Java Community Process)提出新增一个标准化技术规范的正式请求。任何人都可以提交JSR,以向Java平台增添新的API和服务。JSR已成为Java界的一个重要标准。
JSR-303 是JAVA EE 6 中的一项子规范,叫做Bean Validation,Hibernate Validator 是 Bean Validation 的参考实现 . Hibernate Validator 提供了 JSR 303 规范中所有内置 constraint(约束) 的实现,除此之外还有一些附加的 constraint。
官网:Jakarta Bean Validation 3.0
2、引入依赖
JSR303可以单独使用,也可以跟Spring boot整合使用
1)JSR单独使用依赖包:单独引入jdk提供的 validation
<!--引入数据校验依赖 -->
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
2)JSR整合spring boot 依赖包(常用):
此时要注意springboot与validation 的版本号,若当前
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-validation -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
注意:其实 spring-boot-starter-validation 也是为了引入下面2个依赖,主要是 hibernate-validator
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
<version>2.0.2</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.0.18.Final</version>
<scope>compile</scope>
<exclusions>
<exclusion>
<artifactId>validation-api</artifactId>
<groupId>javax.validation</groupId>
</exclusion>
</exclusions>
</dependency>
3、常用注解
1)JSR提供的注解在包javax.validation.constraints包下边,如下图所示:
2)常用注解说明:
注解 | 说明 |
---|---|
@Null | 用于验证对象为null |
@NotNull | 用于验证对象不为null |
@NotBlank | 用于验证字符串不为空(只用于字符串) |
@NotEmpty | 用于校验集合容器,String字符串不能为null,且size>0(带空格的字符串校验不出来) |
@AssertTrue | 用于验证被标注的字段为true |
@AssertFalse | 用于验证被标注的字段为false |
@Min(value) | 用于验证被标注的属性值最小是value |
@Max(value) | 用于验证被标注的属性值最大是value |
@DecimalMin(value) | 被标注的元素必须是一个数字(包含CharSequence),其数值必须大于等于指定的value |
@DecimalMax(value) | 被标注的元素必须是一个数字(包含CharSequence),其数值必须小于等于指定的value |
@Digits(integer, fraction) | 被标注的元素必须是一个数字,其值必须在可接受的范围内 |
@Size(min,max) | 被标注的元素是一个容器对象或String,判断容器对象(Array、Collection、map)和 字符串String 的长度是否在指定范围之内 |
@Length(min,max) | 判断字符串String 的长度是否在指定范围之内 |
@Pattern(regexp) | 判断被标注的元素值是否符合正则表达式regexp |
用于判断被标注的字段是否符合邮箱格式 | |
@Past | 被注释的元素必须是一个过去的日期 |
@Future | 被注释的元素必须是一个将来的日期 |
@Valid | 标记注解,表示开启JSR303校验 |
@Validated | 标记注解,表示开启JSR303校验 |
4、注解@Valid 和 @Validated 的区别
@Valid 和 @Validated 都是标记注解,目的都是开启 validation 校验
4.1)@Valid
由JDK提供的,是标准的JSR303标准
不能分组校验(@Valid 是无参数的)
可以作用在方法、构造函数、方法参数和属性字段上
可以独自完成级联校验
4.2)@Validated
由spring提供
可以作用在类型、方法和方法参数上,不能作用在属性字段上
由于无法作用在属性上,所以无法独自完成级联校验(需要跟@Valid 组合完成级联校验)
支持分组校验
4.3)级联校验(嵌套校验)测试
@Data
public class Person {
@NotNull(message = "名称不能为空")
private String name;
@NotBlank(message = "手机号不能为空")
private String phone;
@Min(value = 1,message = "年龄一定大于0")
private int age;
private String email;
@Valid //开启嵌套校验
//@NotNull(message = "son不能为空") //只有触发当前字段校验时,才会进行嵌套校验(即校验Son的属性),但测试后不需要触发也可以校验Son 中的属性
private List<Son> sons;
}
@Data
public class Son implements Serializable {
@NotBlank(message = "son名称不能为空")
}
//controller类:
@RestController
@RequestMapping("/product/voa")
public class VolationTestController {
//BindingResult绑定了数据 category 的校验结果
@RequestMapping("/test")
public R test(@Validated @RequestBody Person person, BindingResult result){
System.out.println(person.toString());
if(result.hasErrors()){
Map<String,String> errMap = new LinkedHashMap<>();
result.getAllErrors().forEach( err -> {
String key = err.getObjectName();
String value = err.getDefaultMessage();
errMap.put(key,value);
});
return R.error().put("error",errMap);
}else {
return R.ok();
}
}
}
//R是统一应答类
public class R extends HashMap<String,Object> implements Serializable {
private static final long serialVersionUID = 1L;
public R() {
put("code", 0);
put("msg", "success");
}
public static R error() {
return error(HttpStatus.SC_INTERNAL_SERVER_ERROR, "未知异常,请联系管理员");
}
public static R error(String msg) {
return error(HttpStatus.SC_INTERNAL_SERVER_ERROR, msg);
}
public static R error(int code, String msg) {
R r = new R();
r.put("code", code);
r.put("msg", msg);
return r;
}
public static R ok(String msg) {
R r = new R();
r.put("msg", msg);
return r;
}
public static R ok(Map<String, Object> map) {
R r = new R();
r.putAll(map);
return r;
}
public static R ok() {
return new R();
}
public R put(String key, Object value) {
super.put(key, value);
return this;
}
}
总结:
级联校验时,在controller 方法中加上 @Valid/@Validated 开启对指定对象的validation校验,
然后在需要进行级联校验的属性上加上注解@Valid开启级联校验,
注意:
一般进行级联校验的属性上(即@Valid标注的属性)需要触发validation校验时
(即该字段 需要标注validation注解)
才会触发级联校验;但经过测试发现 @Valid标注的属性不加validation注解也可
以触发级联校验,如下边代码:
@Valid //开启嵌套校验 //@NotNull(message = "son不能为空") //只有触发当前字段校验时,才会进行嵌套校验(即校验Son的属性),但测试后不需要触发也可以校验Son 中的属性 private List<Son> sons;
4.4)分组校验
分组校验时,在controller方法中只能用@Valicated 开启voledation 校验
//1、首先创建2个接口用来标记分组
//标记分组
public interface First {
}
//标记分组
public interface Second {
}
//2、在volidation 注解中的group 属性指定分组
@Data
public class Person {
@NotBlank(message = "First 名称不能为空",groups = {First.class})
@NotBlank(message = "Second 名称不能空",groups = {Second.class})
private String name;
}
//3、在controller 方法中使用@Validated 开启voldation校验,并在@Validated中指定当前进行校验的分组
@RestController
@RequestMapping("/product/voa")
public class VolationTestController {
@RequestMapping("/test")
public R test(@Validated(value = Second.class) @RequestBody Person person, BindingResult result){
System.out.println(person.toString());
if(result.hasErrors()){
Map<String,String> errMap = new LinkedHashMap<>();
result.getAllErrors().forEach( err -> {
String key = err.getObjectName();
String value = err.getDefaultMessage();
errMap.put(key,value);
});
return R.error().put("error",errMap);
}else {
return R.ok();
}
}
}
注意:
在分组校验清空下(即在@Validated中指定了分组),若在校验对象中的字段上
的注解没有指定分组,则不触发校验,
如:在 Person 中,若@NotBlank(message = "First 名称不能为空") 不指定分组则
不会触发校验
5、自定义voldation 校验注解
5.1)ConstraintValidator 接口
volidation 注解的处理器需要实现接口 ConstraintValidator,ConstraintValidator结构如下:
public interface ConstraintValidator<A extends Annotation, T> {
default void initialize(A constraintAnnotation) {
}
boolean isValid(T var1, ConstraintValidatorContext var2);
}
实现ConstraintValidator 接口时需要指定2个泛型:
A:是一个注解,即我们自定义的注解
T:我们处理的类型(即我们自定义注解需要处里的类型)
5.2)自定义volidation 注解代码如下:
(1)定义一个注解@StringVal,用来判断字符串是否由自定字符组成
@StringVal 除了自定义注解的通用的@Documented、@Retention、@Target这三个注解
之外,还多了一个注解@Constraint,该注解是用来指定@StringVal 的处理器类。
@Documented
@Retention(RetentionPolicy.RUNTIME) //运行时执行
@Target({ElementType.METHOD,ElementType.FIELD,ElementType.CONSTRUCTOR,ElementType.PARAMETER,ElementType.ANNOTATION_TYPE,ElementType.TYPE_USE})
@Constraint(validatedBy = {StringValValidator.class})
public @interface StringVal {
String validVal();
//下边是 valodation 校验注解默认的三属性(值)
String message() default "volidation test message";
Class<?>[] group() default {};
Class<? extends Payload>[] payload() default {};
}
注意:
@Constraint 只能用于volidation,因为@Constraint 的值只能是 ConstraintValidator 的子类;
@Constraint 结构如下:
@Documented
@Target({ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Constraint {
Class<? extends ConstraintValidator<?, ?>>[] validatedBy();
}
(2)定义@StringVal 的处理器类 StringValValidator
public class StringValValidator implements ConstraintValidator<StringVal,String> {
//
private String vaidVal;
public StringValValidator(){}
@Override
public void initialize(StringVal ann) {
vaidVal = ann.validVal();
}
@Override
public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
if(s == null || "".equals(s.trim())){
return false;
}
for(int i=0;i<s.length();i++){
String c = String.valueOf(s.charAt(i));
if(vaidVal.indexOf(c) < 0){
return false;
}
}
return true;
}
}