Spring Boot之validate_chinanlu0233的博客-CSDN博客

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进行验证。

Valid 参数校验

  1. 定义 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;    }}
  1. 控制层处理
@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 参数校验

Validated是 Spring 对 Valid 的封装,是 Valid 的加强版,支持更多特性

  1. Validated 支持对 PathVariable 参数校验,以及 RequestParam 参数校验

注意点:注解必须写在类上

@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异常

  1. 分组验证

可以对同一个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测试结果如下:

    • *

参数校验异常处理

  1. 比较 low 的方式是在接口中使用 BindingResult 对象去接受这个校验结果
  2. 统一异常处理
@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
标签: 无

请先后发表评论
  • 最新评论
  • 总共0条评论