一、简介
1、快速失败(Fail Fast)
Spring Validation 默认会校验完所有字段,然后才抛出异常。但通常情况下我们希望遇到校验异常就立即返回,此时可以通过一些简单的配置,开启 Fali Fast 模式,一旦校验失败就立即返回。
@Configuration public class ValidatorConfiguration { @Bean public Validator validator() { ValidatorFactory validatorFactory = Validation.byProvider(HibernateValidator.class) .configure() // 快速失败模式 .failFast(true) .buildValidatorFactory(); return validatorFactory.getValidator(); } }
二、单字段类入参校验
这类主要依赖于@RequestParam
进行入参的校验
@Target({ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface RequestParam { @AliasFor("name") String value() default ""; @AliasFor("value") String name() default ""; boolean required() default true; String defaultValue() default "\n\t\t\n\t\t\n\ue000\ue001\ue002\n\t\t\t\t\n"; }
案例:
@GetMapping("/test") public void test(@RequestParam(value = "id", required = true) String id, @RequestParam(value = "name", required = false) String name) { log.info("id,:{}", id); }
value
:请求中传入参数的名称,如果不设置后台接口的value值,则会默认为该变量名。比如上图中第一个参数如果不设置value=“page”,则前端传入的参数名必须为pageNum,否则在后台接口中pageNum将接收不到对应的数据required
:该参数是否为必传项。默认是true,表示请求中一定要传入对应的参数,否则会报404错误,如果设置为false时,当请求中没有此参数,将会默认为null,而对于基本数据类型的变量,则必须有值,这时会抛出空指针异常。如果允许空值,则接口中变量需要使用包装类来声明。defaultValue
:参数的默认值,如果请求中没有同名的参数时,该变量默认为此值。注意默认值可以使用SpEL表达式,如"#{systemProperties[‘java.vm.version’]}"
三、JSON实体类校验
1、注解解析
validation-api
中的注解
注解 | 说明 | 适用类型 |
@AssertFalse | 限制必须是false | boolean Boolean:not null时才校验 |
@AssertTrue | 限制必须是true | boolean Boolean:not null时才校验 |
@Max(value) | 限制必须为一个小于等于value指定值的整数,value是long型 | byte/short/int/long/float/double及其对应的包装类; 包装类对象not null时才校验 |
@Min(value) | 限制必须为一个大于等于value指定值的整数,value是long型 | byte/short/int/long/float/double及其对应的包装类; 包装类对象not null时才校验 |
@DecimalMax(value) | 限制必须小于等于value指定的值,value是字符串类型 | byte/short/int/long/float/double及其对应的包装类; 包装类对象not null时才校验 |
@DecimalMin(value) | 限制必须大于等于value指定的值,value是字符串类型 | byte/short/int/long/float/double及其对应的包装类; 包装类对象not null时才校验 |
@Digits(integer, fraction) | 限制必须为一个小数(其实整数也可以),且整数部分的位数不能超过integer,小数部分的位数不能超过fraction。integer和fraction可以是0。 | byte/short/int/long/float/double及其对应的包装类; 包装类对象必须not null时才校验 |
@Null | 限制只能为null | 任意对象类型(比如基本数据类型对应的包装类、String、枚举类、自定义类等); 不能是8种基本数据类型 |
@NotNull | 限制必须不为null | 任意类型(包括8种基本数据类型及其包装类、String、枚举类、自定义类等); 但是对于基本数据类型,没有意义 |
@Size(min, max) | 限制Collection类型或String的长度必须在min到max之间,包含min和max | Collection类型(List/Set) String |
@Pattern(regexp) | 限制必须符合regexp指定的正则表达式 | String |
@Future | 限制必须是一个将来的日期 | Date/Calendar |
@Past | 限制必须是一个过去的日期 | Date/Calendar |
@Valid | 校验任何非原子类型,标记一个对象,表示校验对象中被注解标记的对象(不支持分组功能) | 需要校验成员变量的对象,比如@ModelAttribute标记的接口入参 |
2、案例
1、简单校验
@PostMapping("/test") public void test(@Validated @RequestBody TestTableDTO dto) { log.info(JSON.toJSONString(dto)); }
import lombok.Data; import javax.validation.constraints.*; @Data public class TestTableDTO { @NotNull(message = "id:为空") private Integer id; // 能通过("name":"",) @NotNull(message = "name:为空") private String name; @NotBlank(message = "address:为空") private String address; @Max(value = 10, message = "sex:大于10") private Integer sex; @Min(value = 90, message = "weight:小于90") private Integer weight; @Size(min = 120, max = 150, message = "height:范围120~150") private Integer height; }
2、分组校验
分局不同的校验组,进行不同规则校验
@PostMapping("/test") public void test(@Validated(value = InfoGroup.class) @RequestBody TestTableDTO dto) { log.info(JSON.toJSONString(dto)); } @PostMapping("/get") public void get(@Validated(value = StringGroup.class) @RequestBody TestTableDTO dto) log.info(JSON.toJSONString(dto)); }
校验实体类
import lombok.Data; import javax.validation.constraints.*; @Data public class TestTableDTO { @NotNull(message = "id:为空") private Integer id; @NotBlank(message = "address:为空",groups = InfoGroup.class) private String address; @Max(value = 10, message = "sex:大于10",groups = StringGroup.class) private Integer sex; }
规则类InfoGroup
:
public interface InfoGroup { }
规则类StringGroup
:
public interface StringGroup { }
3、嵌套校验
Controller
@PostMapping("/test") public void test(@Validated @RequestBody TestTableDTO dto) { log.info(JSON.toJSONString(dto)); }
实体类:
import lombok.Data; import javax.validation.Valid; import javax.validation.constraints.Max; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; import java.util.List; @Data public class TestTableDTO { @NotNull(message = "id:为空") private Integer id; @NotBlank(message = "address:为空", groups = InfoGroup.class) private String address; @Max(value = 10, message = "sex:大于10", groups = StringGroup.class) private Integer sex; @Valid private List<Course> course; @Data public static class Course { @NotBlank(message = "code:为空") private String code; @NotBlank(message = "name:为空") private String name; } }
入参
{ "id": 1, "name": "ddd", "address": "1", "sex": 9, "course": [ { "code": "001", "name": "张三" }, { "code": "002", "name": "李四" } ] }
4、集合校验
如果接口请求体直接传递 JSON 数组给后台,并希望对数组中的每一项都进行参数校验。此时,如果我们直接使用 java.util.Collection 下的 List 或者 Set 来接收数据,参数校验并不会生效。在这种情况下,我们需要使用自定义的 List 集合来接收参数,即包装 List 类型,并声明 @Valid 注解。
不校验类型(错误)
@PostMapping("/test") public void test(@Validated @RequestBody List<TestTableDTO> dto) { log.info(JSON.toJSONString(dto)); }
正解
Controller
@PostMapping("/test") public void test(@Validated @RequestBody StudentList<TestTableDTO> dto) { log.info(JSON.toJSONString(dto)); }
实体类:
import lombok.Data; import javax.validation.constraints.Max; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; @Data public class TestTableDTO { @NotNull(message = "id:为空") private Integer id; @NotBlank(message = "address:为空") private String address; @Max(value = 10, message = "sex:大于10") private Integer sex; }
入参
[ { "id": 1, "address": "1", "sex": 9 }, { "id": 1, "address": "", "sex": 9 } ]
报错信息
系统异常:JSR-303 validated property 'list[1].address' does not have a corresponding accessor for Spring data binding - check your DataBinder's configuration (bean property versus direct field access)
5、自定义校验
自定义注解@ListValue
import javax.validation.Constraint; import javax.validation.Payload; import java.lang.annotation.*; /** * 自定义校验注解 */ @Documented @Constraint(validatedBy = {ListValueConstraintValidator.class}) // 指定校验器,这里不指定时,就需要在初始化时指定 @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE}) @Retention(RetentionPolicy.RUNTIME) public @interface ListValue { /** * 默认的提示内容 */ String message() default "必须提交指定的值哦"; /** * 校验分组 */ Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; /** * 默认值(用于校验器中,数值校验) */ int[] values() default {}; }
自定义校验器ListValueConstraintValidator
import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; import java.util.HashSet; import java.util.Set; /** * 指定校验器 */ public class ListValueConstraintValidator implements ConstraintValidator<ListValue, Integer> { private final Set<Integer> initSet = new HashSet<>(); /** * 初始化方法(将取注解默认值,存到指定位置) */ @Override public void initialize(ListValue constraintAnnotation) { int[] values = constraintAnnotation.values(); for (int value : values) { initSet.add(value); } } /** * 判断是否校验成功 * * @param value 被校验参数 * @param context 上下文 * @return */ @Override public boolean isValid(Integer value, ConstraintValidatorContext context) { System.out.println(value); return initSet.contains(value); } }
入参实体类
import lombok.Data; import javax.validation.constraints.NotBlank; @Data public class TestTableDTO { @ListValue(message = "id:为空", values = 1) private Integer id; @NotBlank(message = "name:为空") private String name; }
Controller层
@PostMapping("/test") public void test(@Validated @RequestBody TestTableDTO dto) { log.info(JSON.toJSONString(dto)); }
测试
PostMan请求
{ "id":null, "name":"张三" }
测试结果:
四、相关
1、源码文件
https://download.csdn.net/download/weixin_44624117/87796980