在本文中,我将向您展示如何使用新的Bean验证框架(又名JSR-303)。
遗产
在得到JSR 303(又名Bean验证框架)的结果之前,有两次有趣的尝试尝试验证框架。 每一个都来自层的末端,并专注于其范围。
前端验证
Struts是2001-2002年在表示层上学习和使用的框架。 Struts使用MVC模型并专注于Controller,在带有Action
Struts中表示。 视图是普通的JSP,Struts使用ActionForm
以便将数据从Controller传递到View,反之亦然。 简而言之,这些是框架用来与View交互的POJO。
作为表示层框架,Struts关注的是验证用户输入。 动作表单有一个漂亮的方法,称为validate()
。 此方法的签名如下:
publicActionErrorsvalidate(ActionMappingmapping,HttpServletRequestrequest)
开发人员必须检查表单是否有效,然后填写(如果不是) ActionErrors
对象(基本上是List
)。 然后,如果ActionErrors
对象不为空,Struts会将流重定向到错误页面(输入)。 由于手动检查很无聊且容易出错,因此使这种验证自动化可能是一个好主意。 即使在那时,声明式验证也被认为是关键。
这是Apache Commons Validator的目标。 其配置通过XML进行。 您指定:
- 您有权访问的验证器。 有一些内置的,但您可以添加自己的
- bean和验证器之间的关联:将通过哪些规则验证哪些bean
尽管Struts紧密集成了Commons Validator,但是您可以完全单独使用后者。 但是,最新的稳定版本(1.3.1)于2006年底发布。当前开发的版本是1.4,但是Maven网站自2008年初以来没有进行过更新。我的做法有些遗漏,因此我将其排除在外当我被迫使用Struts时,可以节省我的验证需求。
在这种情况下,我必须使用它,因为Struts插件知道如何使用两个XML配置文件来生成JavaScript客户端验证。
后端验证
以前,我们看到第一个验证框架来自用户输入。 另一方面,由于在数据库中强制执行了约束,因此插入/更新数据不需要进行此类验证。 例如,尝试将50个字符长的字符串插入VARCHAR(20)
列将失败。
但是,让数据库处理验证有两个主要缺点:
- 由于您需要连接到数据库,发送请求并处理错误,因此会降低性能
- 这种错误不能轻易地映射到Java异常,如果可能的话,也可以映射到错误的特定属性
最后,在将数据发送到数据库之前,最好在Java世界中验证域模型。 这就是Hibernate Validator的范围。 Commons Validator配置基于XML,而Hibernate Validator配置基于Java 5注释。
即使Hibernate Validator旨在验证域模型,您也可以使用它来验证任何bean。
JSR 303 Bean验证
最终,JSR 303取得了成果。 有两个重要的事实:它是不可知的,这意味着您可以在任何喜欢的地方使用它(前端,后端甚至是DTO(如果您遵循此模式)),并且其参考实现是Hibernate Validator v4。
JSR 303的功能包括:
- 在两个不同级别上进行验证:属性或整个bean。 Hibernate Validator不可能做到这一点(因为它是面向数据库的),而Commons Validator却有很多限制
- i18n准备就绪,消息已参数化
- 可使用您自己的验证器扩展
- 可使用注释或XML进行配置。 在下面,将仅显示注释配置
在JSR 303中,验证是以下各项之间交互作用的结果:
- 注释本身。 有些是JSR 303随附的,但是您可以构建自己的
- 将验证带注释的bean的类
最简单的例子
最简单的示例可能包括在类的属性上设置非空约束。 这样做很简单:
publicclassPerson{
privateStringfirstName;
@NotNull
publicStringgetFirstName(){
returnfirstName;
}
// setter
}
请注意,可以将@NotNull
批注放置在属性或getter上(就像在JPA中一样)。 如果使用Hibernate,它也可以使用JSR 303注释来创建/更新数据库模式。
现在,为了验证此bean的实例,您要做的就是:
Set<ConstraintViolation<Person>>violations=validator.validate(person);
如果集合为空,则验证成功,否则,验证失败:其原理与之前的两个框架非常相似。
有趣的是,规范强制要求约束被继承。 因此,如果User
类继承自Person
,则其firstName
属性也将具有非null约束。
约束组
在表示层上,您可能不得不在两个不同的上下文(例如创建和更新)中使用相同的Form bean。 在这两种情况下,您都有不同的约束。 例如,创建个人资料时,用户名是必填项。 更新时无法更改,因此无需验证。
Struts(及其忠实的Commons Validator)通过将验证规则与Java类而不是映射相关联来解决此问题,因为它的范围是前端。 使用注释时,这是不可能的。 为了简化bean的重用,JSR 303引入了约束分组。 如果未指定任何内容(如前所述),则约束将分配给默认组,而在验证时,将在默认组中进行分配。
您还可以按如下约束指定组:
publicclassPerson{
privateStringfirstName;
@NotNull(groups=DummyGroup)
publicStringgetFirstName(){
returnfirstName;
}
// setter
}
因此,这将验证:
Personperson=newPerson();
// Empty set
Set<Constraintviolation<Person>>violations=validator.validate(person);
这还将:
Personperson=newPerson();
// Empty set
Set<Constraintviolation<Person>>violations=validator.validate(person,Default.class);
而且这不会:
Personperson=newPerson();
// Size 1 set
Set<Constraintviolation<Person>>violations=validator.validate(person,DummyGroup.class);
自定义约束
完成内置约束(和Hibernate扩展)的使用后,您可能需要开发自己的约束。 这很简单:约束是使用@Constraint
注释的注释。 让我们创建一个约束来检查未大写的字符串:
@Target({METHOD,FIELD,ANNOTATION_TYPE})
@Retention(RUNTIME)
@Constraint(validatedBy=CapitalizedValidator.class)
public@interfaceCapitalized{
Stringmessage()default"{ch.frankel.blog.validation.constraints.capitalized}";
Class<?>[]groups()default{};
Class<?extendsPayload>[]payload()default{};
}
这三个元素分别用于国际化,分组(请参见上文)和传递元数据。 这些都是强制性的:如果未定义,则该框架将无法工作! 也可以添加更多元素,例如参数化验证: @Min
和@Max
约束使用此元素。
注意,没有什么可以阻止将约束应用于实例而非属性,这是由@Target
定义的,这是一种设计选择。
接下来是验证类。 它必须实现ConstraintValidator<?,?>
:
publicclassCapitalizedValidatorimplementsConstraintValidator<Capitalized,String>{
publicvoidinitialize(Capitalizedcapitalized){}
publicbooleanisValid(Stringvalue,ConstraintValidatorContextcontext){
returnvalue==null||value.equals(WordUtils.capitalizeFully(value));
}
}
就这样! 您现在要做的就是使用@Capitalized
注释属性,并使用框架验证实例。 无需注册新创建的验证器。
约束构成
鼓励创建简单的约束,然后组合它们以创建更复杂的验证规则。 为了做到这一点,创建一个新的约束,并用您要组成的约束对其进行注释。 让我们创建一个约束,该约束将验证String
既不为null
也不不大写:
@NotNull
@Capitalized
@Target({METHOD,FIELD,ANNOTATION_TYPE})
@Retention(RUNTIME)
@Constraint(validatedBy={})
public@interfaceCapitalizedNotNull{
Stringmessage()default"{ch.frankel.blog.validation.constraints.capitalized}";
Class<?>[]groups()default{};
Class<?extendsPayload>[]payload()default{};
}
现在,用它注释属性,并观看魔术的发生!
当然,如果要防止约束组合,则必须限制@Target
值以排除ANNOTATION_TYPE
。
结论
本文仅涉及JSR 303的表面。不过,我希望它是对它的功能的很好的介绍,并希望您有进一步研究它的愿望。
您可以在此处找到Eclipse / Maven格式的本文的源(以及更多)。