SpringBoot在内部通过集成hibernate-validation 已经实现了JSR-349验证规范接口。
一般我们会在进入Controller中校验前端传过来的参数,以往我们会在Controller中使用if判断来进行校验如:
if (EpmUtil.isEmpty(epmApplyVO, epmApplyVO.getId(), epmApplyVO.getRequestParam())) { return BaseResponse.buildFail(PromptMessageConstants.EPM_ERROR_MESSAGE);}
这种做法会让我们的代码中充斥着非空判断,多个方法都要进行重复判断,不优雅。我们可以在VO中进行注解,对封装的Bean进行验证。
/** * @ClassName Minor * @Description 未成年人 * @Author chenhaowen * @Date 2019/4/3 14:39 * @Version 1.0 **/public class Minor { @NotNull(message = "姓名不能为空") private String name; @Max(value = 18, message = "年龄必须小于18岁") private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; }}
@RestController@RequestMapping(value = "/blog/validate")public class ValidateController { @RequestMapping(value = "/goPage") public String goPage(@RequestBody @Valid Minor minor, BindingResult bindingResult) { return bindingResult.hasErrors() ? bindingResult.getFieldError().getDefaultMessage(): "incorrect" ; } }
@Valid 和 BindingResult 是一一对应的,如果有多个@Valid,那么每个@Valid后面跟着的BindingResult就是这个@Valid的验证结果,顺序不能乱
参数检验的注解类:
@Null 只能是null
@NotNull 不能为null 注意用在基本类型上无效,基本类型有默认初始值
@AssertFalse 必须为false
@AssertTrue 必须是true
字符串/数组/集合检查:(字符串本身就是个数组)
@Pattern(regexp="reg") 验证字符串满足正则
@Size(max, min) 验证字符串、数组、集合长度范围
@NotEmpty 验证字符串不为空或者null
@NotBlank 验证字符串不为null或者trim()后不为空
数值检查:同时能验证一个字符串是否是满足限制的数字的字符串
@Max 规定值得上限int
@Min 规定值得下限
@DecimalMax("10.8") 以传入字符串构建一个BigDecimal,规定值要小于这个值
@DecimalMin 可以用来限制浮点数大小
@Digits(int1, int2) 限制一个小数,整数精度小于int1;小数部分精度小于int2
@Digits 无参数,验证字符串是否合法
@Range(min=long1,max=long2) 检查数字是否在范围之间 这些都包括边界值
日期检查:Date/Calendar
@Post 限定一个日期,日期必须是过去的日期
@Future 限定一个日期,日期必须是未来的日期
其他验证:
@Vaild 递归验证,用于对象、数组和集合,会对对象的元素、数组的元素进行一一校验
@Email 用于验证一个字符串是否是一个合法的邮件地址,空字符串或null算验证通过
@URL(protocol=,host=,port=,regexp=,flags=) 用于校验一个字符串是否是合法URL
Validated是 Spring 对 Valid 的封装,是 Valid 的加强版,支持更多特性
注意点:注解必须写在类上
@RestController// 注解必须写在这里,参数校验不过会有 ConstraintViolationException 异常@Validatedpublic class Valid2Controller { @GetMapping("valid4/{data}") public String getPathVariable(@Size(min = 3, max = 6) @PathVariable String data) { return data; } @GetMapping("valid4") public String getRequestParam(@Size(min = 3, max = 6) @RequestParam(value = "name", defaultValue = "0") String name) { return name; } }
检验不过会出现ConstraintViolationException.class异常
可以对同一个bean应对不同情况的校验
拟定一个场景:数据接口有两个未成年和成年人,我们需要对基本信息做校验,比如未成年人应该大于等于18岁,成年人则小于18岁
定义两个接口来分组:
/** * @Author chenhaowen * @Description //成年人分组 * @Date 2019/4/3 16:49 **/public interface Adult extends Default {} /** * @Author chenhaowen * @Description //未成年人分组 * @Date 2019/4/3 16:49 **/public interface Juveniles extends Default {}
注意点:接口必须继承Default,一些校验项无法校验。
定义Person类及校验项
/** * @ClassName Person * @Description 人 * @Author chenhaowen * @Date 2019/4/3 16:51 * @Version 1.0 **/public class Person { @NotNull(message = "姓名不能为空") private String name; @Max(value = 17,message = "未成年人应该未满18岁",groups = {Juveniles.class}) @Min(value = 18,message = "成年人应该已满18岁",groups = {Adult.class}) private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; }}
上面我们将年龄这个属性进行了分组,分别对应Adult和Juveniles
定义Controller接口
@RequestMapping(value = "/adult") public String adult(@RequestBody @Validated(value = {Adult.class}) Person person, BindingResult bindingResult){ return bindingResult.hasErrors() ? bindingResult.getFieldError().getDefaultMessage(): "incorrect" ; } @RequestMapping(value = "/minor") public String minor(@RequestBody @Validated(value = {Juveniles.class}) Person person, BindingResult bindingResult){ return bindingResult.hasErrors() ? bindingResult.getFieldError().getDefaultMessage(): "incorrect" ; }
post测试结果如下:
@RestControllerAdvicepublic class CheckAdvice { private static final ObjectMapper MAPPER = new ObjectMapper(); /** * 请求的 JSON 参数在请求体内的参数校验 * * @param e 异常信息 * @return 返回数据 * @throws JsonProcessingException jackson 的异常 */ @ResponseStatus(HttpStatus.BAD_REQUEST) @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity<String> handleBindException1(MethodArgumentNotValidException e) throws JsonProcessingException { e.getBindingResult().getAllErrors().forEach(System.out::println); return new ResponseEntity<>("cuowu:" + MAPPER.writeValueAsString(e.getBindingResult().getAllErrors()), HttpStatus.BAD_REQUEST); } /** * 请求的 URL 参数检验 * * @param e 异常信息 * @return 返回提示信息 */ @ResponseStatus(HttpStatus.BAD_REQUEST) @ExceptionHandler(ConstraintViolationException.class) public String handleBindException2(ConstraintViolationException e) { e.getConstraintViolations().forEach(System.out::println); return "ConstraintViolationException"; } }
上述类可以拦截Controller类抛出异常并处理
在很多情况下,JSR303 不能满足我们的校验需求,我们需要自定义一些校验逻辑,当然不是使用 if else 去判断。可以定义一些注解以及注解处理工具嵌入到 Spring 框架中自动调用。
这里我们定义了注解 AnnoValidator 用来校验 anno 字段,只能存放1,2,3之中的一个字符串数字
@AnnoValidator(value = "1,2,3") private String anno;
注解内容:
@Documented@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})//指定注解的处理类@Constraint(validatedBy = AnnoValidatorClass.class)public @interface AnnoValidator { String value(); String message() default "AnnoValidator 不存在"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
注解处理类
public class AnnoValidatorClass implements ConstraintValidator<AnnoValidator, Object> { private String value; @Override public void initialize(AnnoValidator constraintAnnotation) { this.value = constraintAnnotation.value(); } @Override public boolean isValid(Object o, ConstraintValidatorContext constraintValidatorContext) { List<String> list = Arrays.asList(value.split(",")); final AtomicBoolean flag = new AtomicBoolean(false); list.forEach(one -> { if (one.equals(o)) { flag.set(true); } }); return flag.get(); } }
经常某些字段的校验数据是动态改变的(例如手机的号码段扩充,某些操作的操作码增加),所以这些特殊情况我们不能讲校验的数据写死到代码中。下面展示了动态改变校验数据的方法。
自定义校验注解
@Documented@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})//指定注解的处理类@Constraint(validatedBy = DynamicHandler.class)public @interface Dynamic { String value() default ""; String message() default "Dynamic 不存在"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {};}
需要校验的实体类,假设 books 内的数据只能在某个范围内
@Datapublic class UserDynamic { private String name; @Dynamic private Set<String> books; }
注解的处理类。需要注意的点:
1.被 static 关键字修饰的字段是属于整个类的;
2.存储可变字段的 Set 必须是线程安全的,当对容器中元素进行遍历同时增加数据时会抛出 fail-fast 错误。
public class DynamicHandler implements ConstraintValidator<Dynamic, Set<String>> { // 得用线程安全的容器,当对容器中元素进行遍历同时增加数据时会抛出 fail-fast 错误 private volatile static CopyOnWriteArraySet<String> dynamicSet; @Override public boolean isValid(Set<String> set, ConstraintValidatorContext constraintValidatorContext) { return dynamicSet.containsAll(set); } @Override public void initialize(Dynamic constraintAnnotation) { // nothing to do } public static void setSet(CopyOnWriteArraySet<String> set) { DynamicHandler.dynamicSet = set; }}
使用定时任务来模拟校验数据的改变,每十秒钟改变一下校验的数据。真实环境中应该是从数据库中获取
@Slf4j@Component@EnableSchedulingpublic class DynamicSchedule { @Scheduled(fixedDelay = 10000) public void autoSync() { CopyOnWriteArraySet<String> dynamicSet = new CopyOnWriteArraySet<>(); Random random = new Random(); for (int i = 0; i < 3; i++) { dynamicSet.add(random.nextInt(10) + ""); } DynamicHandler.setSet(dynamicSet); log.info(dynamicSet.toString()); } }
原网址: 访问
创建于: 2020-12-23 17:43:14
目录: default
标签: 无
未标明原创文章均为采集,版权归作者所有,转载无需和我联系,请注明原出处,南摩阿彌陀佛,知识,不只知道,要得到
最新评论