Spring Validation 介绍

Spring Validation 介绍

环境搭建

pom.xml 中引入依赖:

Springboot-2.3 之前的版本只需引入 web 依赖即可。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>

简单使用

对实体类

此处使用 Lombok

校验注解解析

@Data
public class User {
    @NotBlank(message = "用户名不能为空")
    private String userName;
    @NotNull(message = "性别不能为空")
    private Integer sex;
    @Length(max = 20, message = "备注长度不能超过20")
    private String remark;
}

对接口

@RestController
public class UserController {
    @GetMapping("/user")
    public String hello(@Valid User user) {
        return user.toString();
    }
}

测试

访问该接口,传递不符合校验注解的数据

可以看到,没有通过校验的异常信息会在控制台打印出来

BindingResult

未使用 BindingResult 进行数据校验时,若校验不通过,系统会报异常,访问失败

使用 BindingResult 进行数据校验时,若校验不通过,错误信息会存储在其中而系统不会报异常,访问成功

注意:一个校验注解后面跟一个 BindingResult,若有多个校验注解,需要每个都对应一个 BindingResult

如下代码演示使用 BindingResult 并打印出其存储的校验失败信息:

@RestController
public class UserController {
    @GetMapping("/user")
    public String hello(@Valid User user, BindingResult result) {
        List<FieldError> fieldErrors = result.getFieldErrors();
        if (!fieldErrors.isEmpty()) {
            //打印校验错误信息
            fieldErrors.forEach(x-> System.out.println(x.getDefaultMessage()));
        }
        return user.toString();
    }
}

Validated和Valid

简介

@Valid 是使用 Hibernate validation

@Validated 是使用 Spring Validator

说明:java 的JSR303声明了@Valid 这类接口,而 Hibernate-validator 对其进行了实现

@Validation 对 @Valid 进行了二次封装,在使用上并没有区别,但在分组、注解位置、嵌套验证等功能上有所不同,这里主要就这几种情况进行说明。

区别

@Validated:可以用在在类上、方法上和方法参数上。但不能用于成员属性(field)

@Validated 支持分组校验


@Valid:可以用在方法、构造函数、方法参数和成员属性(field)上

@Valid 支持嵌套校验

分组校验

在日常开发过程中,对于同一个参数,需要在不同场景下应用不同的校验规则,如:

  • 新增时 code 可以为空,修改时 code 不能为空。

使用 @Validated 进行分组校验

实体类

@Data
public class User {
    @NotBlank(message = "code不能为空", groups = {Update.class})
    private String code;
    @NotBlank(message = "用户名不能为空", groups = {Save.class, Update.class})
    private String userName;
    @NotNull(message = "性别不能为空", groups = {Save.class, Update.class})
    private Integer sex;
    @Length(max = 20, message = "备注长度不能超过20", groups = {Save.class, Update.class})
    private String remark;

    public interface Save {
    }

    public interface Update {
    }
}

接口

@RestController
public class UserController {
    @PostMapping("/user")
    public String save(@Validated(User.Save.class) User user, BindingResult result) {
        List<FieldError> fieldErrors = result.getFieldErrors();
        List<String> list = new ArrayList<>();
        if (!fieldErrors.isEmpty()) {
            fieldErrors.forEach(x -> list.add(x.getDefaultMessage()));
        }
        return list.toString();
    }

    @PutMapping("/user")
    public String update(@Validated(User.Update.class) User user, BindingResult result) {
        List<FieldError> fieldErrors = result.getFieldErrors();
        List<String> list = new ArrayList<>();
        if (!fieldErrors.isEmpty()) {
            fieldErrors.forEach(x -> list.add(x.getDefaultMessage()));
        }
        return list.toString();
    }
}

测试

可以看出,修改比新增多校验了 code,分组校验成功

新增

修改

分组校验优化

使用校验注解时,若不指定其 groups ,会有一个默认的组 Default,从这个 Default 入手,优化代码

实体类

新建 Update 组 并继承 Default 组,只对 code 指定为 Update 组,其他不指定

此时不仅会校验 Update 组,未指定组(即 Default 组)的属性也会被校验

@Data
public class User {
    @NotBlank(message = "code不能为空", groups = Update.class)
    private String code;
    @NotBlank(message = "用户名不能为空")
    private String userName;
    @NotNull(message = "性别不能为空")
    private Integer sex;
    @Length(max = 20, message = "备注长度不能超过20")
    private String remark;

    public interface Update extends Default {
    }
}

接口

在编写接口时,如果 Update 继承了 Default,下面两个写法就是等效的:

  1. @Validated(Update.class)
  2. @Validated(Update.class,Default.class)

如果 @Validated 进行校验时没有指定组,等效于 @Validated(Default.class)

  1. @Validated
  2. @Validated(Default.class)
@RestController
public class UserController {
    @PostMapping("/user")
    public String save(@Validated User user, BindingResult result) {
        List<FieldError> fieldErrors = result.getFieldErrors();
        List<String> list = new ArrayList<>();
        if (!fieldErrors.isEmpty()) {
            fieldErrors.forEach(x -> list.add(x.getDefaultMessage()));
        }
        return list.toString();
    }

    @PutMapping("/user")
    public String update(@Validated(User.Update.class) User user, BindingResult result) {
        List<FieldError> fieldErrors = result.getFieldErrors();
        List<String> list = new ArrayList<>();
        if (!fieldErrors.isEmpty()) {
            fieldErrors.forEach(x -> list.add(x.getDefaultMessage()));
        }
        return list.toString();
    }
}

测试

嵌套校验

如果需要校验的对象中包含对象/集合,使用 @Valid 嵌套校验解决校验问题

实体类

Order
@Data
public class Order {
    @NotBlank(message = "order: code 不能为空")
    private String code;
    @Length(max = 1, message = "order:test 最长为1")
    private String test;
}
User
@Data
public class User {
    @NotBlank(message = "user:code不能为空")
    private String code;
    @NotBlank(message = "user:用户名不能为空")
    private String userName;
    @Valid
    @NotNull(message = "Order 对象不能为空")
    private Order order;
}

接口

@RestController
public class UserController {
    @PostMapping("/user")
    public String save(@Validated User user, BindingResult result) {
        List<FieldError> fieldErrors = result.getFieldErrors();
        List<String> list = new ArrayList<>();
        if (!fieldErrors.isEmpty()) {
            fieldErrors.forEach(x -> list.add(x.getDefaultMessage()));
        }
        return list.toString();
    }
}

测试

自定义校验

Spring Validation 允许用户自定义校验,分两步:

  1. 自定义校验注解
  2. 编写校验类

自定义校验手机号

实体类
@Data
public class User {
    @Phone(message = "手机号不合法")
    private String phoneNumber;
}
自定义注解
@Documented
@Constraint(validatedBy = {PhoneValidator.class})
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface Phone {
    //默认错误消息
    String message() default "请输入合法的手机号码";

    //分组
    Class<?>[] groups() default {};

    //负载
    Class<? extends Payload>[] payloads() default {};
}
校验类

正则校验(未测试)

手机号

(^0[0-9]{2,3}[0-9]{8})|(^((\(\d{3}\))|(\d{3}\-))?(1[3456789]\d{9})$)|(^(400)(\d{3})(\d{4}$))|(^(800)(\d{3})(\d{4}$))

座机

^[0][1-9]{2,3}-[0-9]{5,10}$

400

^400-([0-9]){1}([0-9-]{6})([0-9]){1}$
public class PhoneValidator implements ConstraintValidator<Phone, String> {
    /**
     * 正则匹配手机号
     * 校验手机号,号段主要有(不包括上网卡):130~139、150~153,155~159,180~189、170~171、176~178。14号段为上网卡专属号段
     * 如有其他需求,修改此处正则即可
     */
    private static final Pattern PATTERN = Pattern.compile("^((13[0-9])|(17[0-1,6-8])|(15[^4,\\\\D])|(18[0-9]))\\d{8}$");
    private static final Integer PHONE_LENGTH = 11;

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        // 不为空才进行校验
        if (value != null) {
            if (value.length() != PHONE_LENGTH) {
                return false;
            }
            Matcher matcher = PATTERN.matcher(value);
            return matcher.find();
        }
        return true;
    }
}
测试

实战

使用 Spring Validation + AOP 进行数据校验

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值