Valid注解介绍

@Valid 注解类型的使用:

@Null
限制只能为null

@NotNull
限制必须不为null

@AssertFalse
限制必须为false

@AssertTrue
限制必须为true

@DecimalMax(value)
限制必须为一个不大于指定值的数字

@DecimalMin(value)
限制必须为一个不小于指定值的数字

@Digits(integer,fraction)
限制必须为一个小数,且整数部分的位数不能超过integer,小数部分的位数不能超过fraction

@Future
限制必须是一个将来的日期

@Max(value)
限制必须为一个不大于指定值的数字

@Min(value)
限制必须为一个不小于指定值的数字

@Past
限制必须是一个过去的日期

@Pattern(value)
限制必须符合指定的正则表达式

@Size(max,min)
限制字符长度必须在minmax之间

@Past
验证注解的元素值(日期类型)比当前时间早

@NotEmpty
验证注解的元素值不为null且不为空(字符串长度不为0、集合大小不为0

@NotBlank
验证注解的元素值不为空(不为null、去除首位空格后长度为0),不同于@NotEmpty@NotBlank只应用于字符串且在比较时会去除字符串的空格

@Email
验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email格式

注意,不要错用了异常类型,比如在int上不可用@size

Validation的特殊用法

基本用法不说了,网上例子很多,这里主要介绍下比较特殊情况下使用的方法。

  1. 分组

有的时候,我们对一个实体类需要有多中验证方式,在不同的情况下使用不同验证方式,比如说对于一个实体类来的id来说,保存的时候是不需要的,对于更新时是必须的,可以如下配置:

public class UserModel{

	@NotNull(message = "{id.empty}", groups = { First.class })
	private int id;

	@NotNull(message = "{username.empty}", groups = { First.class, Second.class })
	private String username;

	@NotNull(message = "{content.empty}", groups = { First.class, Second.class })
	private String content;

	public int getId(){
		return id;
	}

	public void setId(int id){
		this.id = id;
	}

	public String getUsername(){
		return username;
	}

	public void setUsername(String username){
		this.username = username;
	}

	public String getContent(){
		return content;
	}

	public void setContent(String content){
		this.content = content;
	}
}
public interface First{
}

public interface Second{
}

通过 groups 对验证进行分组

在不同的Action中标记不同的验证分组可以达到验证部分属性目的

controler中的代码如下:

@RequestMapping(value = "/save.action", method = RequestMethod.POST)
public String save(@Validated( { Second.class }) UserModel userModel, BindingResult result) {
	if (result.hasErrors()) {
		return "validate/error";
	}
	return "redirect:/success";
}

@RequestMapping(value = "/update.action", method = RequestMethod.POST)
public String update(@Validated( { First.class, Second.class }) UserModel user, BindingResult result) {
	if (result.hasErrors()) {
		return "validate/error";
	}
	return "redirect:/success";
}
  1. 组序列

默认情况下,不同组别的约束验证是无序的,然而在某些情况下,约束验证的顺序却很重要,如下面两个例子:

(1)第二个组中的约束验证依赖于一个稳定状态来运行,而这个稳定状态是由第一个组来进行验证的。

(2)某个组的验证比较耗时,CPU 和内存的使用率相对比较大,最优的选择是将其放在最后进行验证。因此,在进行组验证的时候尚需提供一种有序的验证方式,这就提出了组序列的概念。

一个组可以定义为其他组的序列,使用它进行验证的时候必须符合该序列规定的顺序。在使用组序列验证的时候,如果序列前边的组验证失败,则后面的组将不再给予验证。

下例中声明了组 GroupA.classGroupB.class 和 Group.class,其中 defaultGroupAGroupB 均为 Group 的序列。

public interface GroupA {
}

public interface GroupB {
}

@GroupSequence( { Default.class, GroupA.class, GroupB.class })
public interface Group {
}

public class User {
	@NotEmpty(message = "firstname may be empty")
	private String firstname;

	@NotEmpty(message = "middlename may be empty", groups = Default.class)
	private String middlename;

	@NotEmpty(message = "lastname may be empty", groups = GroupA.class)
	private String lastname;

	@NotEmpty(message = "country may be empty", groups = GroupB.class)
	private String country;
}

由于已经定义了组的顺序

以下标记验证会按照定义的顺序进行验证

若分组序列中前一个验证未通过,则接下来的验证不会继续进行

@RequestMapping(value = "/update.action", method = RequestMethod.POST)
public String register(@Validated(Group.class) User user, BindingResult result) {
	if (result.hasErrors()) {
		return "validate/error";
	}
	return "redirect:/success";
}
  1. 验证多个对象

当我们在一个功能处理方法上需要验证多个模型对象时,需要通过如下形式来获取验证结果:

@RequestMapping("/validate/multi")
public String multi(@Valid @ModelAttribute("a") A a, BindingResult aErrors, @Valid @ModelAttribute("b") B b, BindingResult bErrors) {

	if (aErrors.hasErrors()) { //如果a模型对象验证失败
		return "validate/error";
	}
	if (bErrors.hasErrors()) { //如果a模型对象验证失败
		return "validate/error";
	}
	return "redirect:/success";
}

每一个模型对象后边都需要跟一个ErrorsBindingResult对象来保存验证结果,其方法体内部可以使用这两个验证结果对象来选择出错时跳转的页面或处理的逻辑。

  1. Junit测试

当自定义拓展Validation时,可以使用如下方法进行测试:

@Test
public void testValidate() {
	AnnotationDescriptor<EqualsAny> descriptor = new AnnotationDescriptor<EqualsAny>(EqualsAny.class);
	EqualsAny equalsAny = AnnotationFactory.create(descriptor);
	EqualsAnyValidator equalsAnyValidator = new EqualsAnyValidator();
	equalsAnyValidator.initialize(equalsAny);
	Assert.assertTrue(equalsAnyValidator.isValid("123", null));
}
Spring支持往ConstraintValidator里面注入bean对象。例如在EqualsAnyValidator中利用@Resource注解注入其他Bean对象

Valid与Validdated的区别

Spring Validation验证框架对参数的验证机制提供了@Validated(Spring's JSR-303规范,是标准JSR-303的一个变种),javax提供了@Valid(标准JSR-303规范),配合BindingResult可以直接提供参数验证结果。其中对于字段的特定验证注解比如@NotNull等网上到处都有,这里不详述

在检验Controller的入参是否符合规范时,使用@Validated或者@Valid在基本验证功能上没有太多区别。但是在分组、注解地方、嵌套验证等功能上两个有所不同:

  1. 分组

@Validated :提供了一个分组功能,可以在入参验证时,根据不同的分组采用不同的验证机制,这个网上也有资料,不详述。@Valid:作为标准JSR-303规范,还没有吸收分组的功能。

  1. 注解地方

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

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

两者是否能用于成员属性(字段)上直接影响能否提供嵌套验证的功能。

  1. 嵌套验证

在比较两者嵌套验证时,先说明下什么叫做嵌套验证。比如我们现在有个实体叫做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;
}

Item带有很多属性,属性里面有属性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框架只会对Itemidprops做非空和数量验证,不会对props字段里的Prop实体进行字段验证,也就是@Validated@Valid加在方法参数前,都不会自动对参数进行嵌套验证。也就是说如果传的List<Prop>中有Proppid为空或者是负数,入参验证不会检测出来。

为了能够进行嵌套验证,必须手动在Item实体的props字段上明确指出这个字段里面的实体也要进行验证。由于@Validated不能用在成员属性(字段)上,但是@Valid能加在成员属性(字段)上,而且@Valid类注解上也说明了它支持嵌套验证功能,那么我们能够推断出:@Valid加在方法参数时并不能够自动进行嵌套验证,而是用在需要嵌套验证类的相应字段上,来配合方法参数上@Validated@Valid来进行嵌套验证。

我们修改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;
}

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

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

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

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

Last modification:May 5, 2022
如果觉得我的文章对你有用,请随意赞赏