点击数:14

前言

有些时候一个对象会在多个场景使用,不同场景对该对象中的参数校验需求不同,即有些场景不对参数进行校验。
比如添加数据时不需要传主键id,而修改保存时必须要传。
这里可以用到校验分组groups

1.分组校验

public class User implements Serializable {

    // 添加2个空接口,用例标记参数校验规则
    /**
     * 注册校验规则
     */
    public interface UserRegisterValidView {
    }

    /**
     * 登录校验规则
     */
    public interface UserLoginValidView {
    }

    private static final long serialVersionUID = 1L;

    @NotBlank(message = "用户名不能为空")
    private String userName;

    @NotBlank(message = "密码不能为空")
    private String password;

    // 若填写了groups,则该参数验证只在对应验证规则下启用
    @Past(groups = { UserRegisterValidView.class }, message = "出生日期不符合要求")
    private Date birthday;

    @DecimalMin(value = "0.1", message = "金额最低为0.1")
    @NotNull(message = "金额不能为空")
    private BigDecimal balance;

    @AssertTrue(groups = { UserRegisterValidView.class, UserLoginValidView.class }, message = "标记必须为true")
    private boolean flag;

    @Min(value = 18, message = "年龄不能小于18")
    private Integer age;
}

声明2个空接口,用来标记不同的校验场景

// 添加2个空接口,用例标记参数校验规则
/**
 * 注册校验规则
 */
public interface UserRegisterValidView extends Default {
 }

/**
 * 登录校验规则
 */
public interface UserLoginValidView extends Default {
}

//控制校验顺序
@GroupSequence({ Default.class, UserLoginValidView.class, UserRegisterValidView.class })
public interface IGroup {
}

未分组的注解默认是分配到Default分组里,我在使用中发现自定义的接口如果不继承Default,在使用时会忽略掉默认Default分组的注解,不知道是官方规定还是只有我遇到的。

出生日期参数,我们只在注册场景校验,我们在其他场景(包含default)一律不进行验证。

// 若填写了groups,则该参数验证只在对应验证规则下启用
@Past(groups = { UserRegisterValidView.class }, message = "出生日期不符合要求")
private Date birthday;

此时的Controller改成

@RequestMapping(value = "/register", method = RequestMethod.POST)
    @ResponseBody//表明对User对象的校验,启用UserRegisterValidView规则
    public CommonResponse register(@Validated(value = { UserRegisterValidView.class }) @RequestBody User user) {
        CommonResponse response = new CommonResponse();
        return response;
    }

2.自定义校验

如果遇到JAR包中没有的验证规则,那么我们需要自定义校验规则:
首先新建个自定义的校验接口(判断是否为QQ邮箱):

@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
@Retention(RUNTIME)
@Documented
@Constraint(validatedBy = IsQQEmailImpl.class) // 指明自定义注解的实现类
public @interface IsQQEmail {

    String message() default "email is invalid";

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

    Class<? extends Payload>[] payload() default {};

    @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER })
    @Retention(RUNTIME)
    @Documented
    public @interface List {
        IsQQEmail[] value();
    }
}

接着写个实现类,实现上面的接口:

public class IsQQEmailImpl implements ConstraintValidator<IsQQEmail, String> {

    @Override
    public void initialize(IsQQEmail isQQEamil) {
        // TODO Auto-generated method stub
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        // TODO Auto-generated method stub

        if (value == null) {
            return false;
        }

        // 进行QQ邮箱格式的简单判断,实际开发用正则
        if (value.endsWith("@qq.com") || value.endsWith("@QQ.COM")) {
            return true;
        }

        return false;

    }

}

最后在需要校验的对象属性,添加这个校验注解即可:

public class User implements Serializable {
    @IsQQEmail(message = "邮箱错误")
    private String email;
}

3.嵌套校验

需要用到注解@Valid

在比较两者嵌套验证时,先说明下什么叫做嵌套验证。比如我们现在有个实体叫做Item:

public class Item {

    @NotNull(message = "id不能为空")
    @Min(value = 1, message = "id必须为正整数")
    private Long id;

    @NotNull(message = "props不能为空")
    @Size(min = 1, message = "至少要有一个属性")
    private List<Prop> props;
}

Prop带有很多属性,属性里面有属性id,属性值id,属性名和属性值,如下所示

public class Prop {

    @NotNull(message = "pid不能为空")
    @Min(value = 1, message = "pid必须为正整数")
    private Long pid;

    @NotNull(message = "vid不能为空")
    @Min(value = 1, message = "vid必须为正整数")
    private Long vid;

    @NotBlank(message = "pidName不能为空")
    private String pidName;

    @NotBlank(message = "vidName不能为空")
    private String vidName;
}

属性这个实体也有自己的验证机制,比如属性和属性值id不能为空,属性名和属性值不能为空等。
现在我们有个ItemController接受一个Item的入参,想要对Item进行验证,如下所示:

@RestController
public class ItemController {

    @RequestMapping("/item/add")
    public void addItem(@Validated Item item, BindingResult bindingResult) {
        doSomething();
    }
}

如果Item实体的props属性不额外加注释,只有@NotNull和@Size,无论入参采用@Validated还是@Valid验证,Spring Validation框架只会对Item的id和props做非空和数量验证,不会对props字段里的Prop实体进行字段验证,也就是@Validated和@Valid加在方法参数前,都不会自动对参数进行嵌套验证。也就是说如果传的List中有Prop的pid为空或者是负数,入参验证不会检测出来。
修改Item类如下所示:

public class Item {

    @NotNull(message = "id不能为空")
    @Min(value = 1, message = "id必须为正整数")
    private Long id;

    @Valid // 嵌套验证必须用@Valid
    @NotNull(message = "props不能为空")
    @Size(min = 1, message = "props至少要有一个自定义属性")
    private List<Prop> props;
}

然后我们在ItemController的addItem函数上再使用@Validated或者@Valid,就能对Item的入参进行嵌套验证。此时Item里面的props如果含有Prop的相应字段为空的情况,Spring Validation框架就会检测出来,bindingResult就会记录相应的错误。

总结一下@Validated和@Valid在嵌套验证功能上的区别:

  • @Validated:用在方法入参上无法单独提供嵌套验证功能。不能用在成员属性(字段)上,也无法提示框架进行嵌套验证。能配合嵌套验证注解@Valid进行嵌套验证。

  • @Valid:用在方法入参上无法单独提供嵌套验证功能。能够用在成员属性(字段)上,提示验证框架进行嵌套验证。能配合嵌套验证注解@Valid进行嵌套验证。


心之所向,素履前往 ;生之逆旅,一苇以航 .