Bean Validation 1.1当前实现是Hibernate validator 5,且spring4才支持。接下来我们从以下几个方法讲解Bean Validation 1.1,当然不一定是新特性:
- 集成Bean Validation 1.1到SpringMVC
- 分组验证、分组顺序及级联验证
- 消息中使用EL表达式
- 方法参数/返回值验证
- 自定义验证规则
- 类级别验证器
- 脚本验证器
- cross-parameter,跨参数验证
- 混合类级别验证器和跨参数验证器
- 组合多个验证注解
- 本地化
规范:http://beanvalidation.org/1.1/spec/
hibernate validator文档:http://hibernate.org/validator/
首先添加hibernate validator 5依赖:
- <dependency>
- <groupId>org.hibernate</groupId>
- <artifactId>hibernate-validator</artifactId>
- <version>5.3.4</version>
- </dependency>
如果想在消息中使用EL表达式,请确保EL表达式版本是 2.2或以上
- <?xml version="1.0" encoding="UTF-8"?>
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:context="http://www.springframework.org/schema/context"
- xmlns:mvc="http://www.springframework.org/schema/mvc"
- xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
- http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
- http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd">
-
- <!-- 启动注解驱动的Spring MVC功能,注册请求url和注解POJO类方法的映射--><!-- <mvc:annotation-driven /> -->
- <mvc:annotation-driven validator="validator" />
-
- <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
- <property name="providerClass" value="org.hibernate.validator.HibernateValidator"/>
- <!--不设置则默认为classpath下的 ValidationMessages.properties -->
- <property name="validationMessageSource" ref="messageSource"/>
- </bean>
- <bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
- <property name="basename" value="classpath:message/validatemessages"/>
- <property name="fileEncodings" value="utf-8"/>
- <property name="cacheSeconds" value="120"/>
- </bean>
-
- </beans>
此处主要把bean validation的消息查找委托给spring的messageSource
。
- public class User implements Serializable {
- @NotNull(message = "{user.id.null}")
- private Long id;
-
- @NotEmpty(message = "{user.name.null}")
- @Length(min = 5, max = 20, message = "{user.name.length.illegal}")
- @Pattern(regexp = "[a-zA-Z]{5,20}", message = "{user.name.illegal}")
- private String name;
-
- @NotNull(message = "{user.password.null}")
- private String password;
- }
- user.id.null=用户编号不能为空
- user.name.null=用户名不能为空
- user.name.length.illegal=用户名长度必须在5到20之间
- user.name.illegal=用户名必须是字母
- user.password.null=密码不能为空
- @Controller
- @RequestMapping(value = "validate")
- public class ValidateController {
-
- @RequestMapping(value="test", method = {RequestMethod.GET})
- @ResponseBody
- public Response test2(@Validated User model, BindingResult result){
-
- if(result.hasErrors()){
- for(int i = 0;i<result.getFieldErrorCount();i++){
- FieldError error = result.getFieldErrors().get(i);
- System.out.println(error.getField() + "-->" + error.getDefaultMessage());
- }
- }
-
- return new Response();
- }
-
- }
其中实体User前必须加上注解
@Validated
和@Valid,以及BindingResult
必须紧随其后,不然会报错。
如果我们想在新增的情况验证id
和name
,而修改的情况验证name
和password
,怎么办? 那么就需要分组了。
首先定义分组接口:
- public interface First {
- }
-
- public interface Second {
- }
分组接口就是两个普通的接口,用于标识,类似于java.io.Serializable
。
接着我们使用分组接口标识实体:
- public class User implements Serializable {
-
- @NotNull(message = "{user.id.null}", groups = {First.class})
- private Long id;
-
- @Length(min = 5, max = 20, message = "{user.name.length.illegal}", groups = {Second.class})
- @Pattern(regexp = "[a-zA-Z]{5,20}", message = "{user.name.illegal}", groups = {Second.class})
- private String name;
-
- @NotNull(message = "{user.password.null}", groups = {First.class, Second.class})
- private String password;
- }
验证时使用如:
- @Controller
- @RequestMapping(value = "validate")
- public class ValidateController {
-
- @RequestMapping(value="test2", method = {RequestMethod.POST})
- @ResponseBody
- public Response test2(@Validated({Second.class}) User model, BindingResult result){
-
- if(result.hasErrors()){
- for(int i = 0;i<result.getFieldErrorCount();i++){
- FieldError error = result.getFieldErrors().get(i);
- System.out.println(error.getField() + "-->" + error.getDefaultMessage());
- }
- }
- return new Response();
- }
-
- }
-
即通过@Validate
注解标识要验证的分组;如果要验证两个的话,可以这样@Validated({First.class, Second.class})
。
接下来我们来看看通过分组来指定顺序;还记得之前的错误消息吗?user.name
会显示两个错误消息,而且顺序不确定;如果我们先验证一个消息;如果不通过再验证另一个怎么办?可以通过@GroupSequence
指定分组验证顺序:
- @GroupSequence({First.class, Second.class, User.class})
- public class User implements Serializable {
- private Long id;
-
- @Length(min = 5, max = 20, message = "{user.name.length.illegal}", groups = {First.class})
- @Pattern(regexp = "[a-zA-Z]{5,20}", message = "{user.name.illegal}", groups = {Second.class})
- private String name;
-
- private String password;
- }
通过@GroupSequence
指定验证顺序:先验证First
分组,如果有错误立即返回而不会验证Second
分组,接着如果First
分组验证通过了,那么才去验证Second
分组,最后指定User.class
表示那些没有分组的在最后。这样我们就可以实现按顺序验证分组了。
另一个比较常见的就是级联验证:
- public class User {
-
- @Valid
- @ConvertGroup(from=First.class, to=Second.class)
- private Organization o;
-
- }
级联验证只要在相应的字段上加@Valid
即可,会进行级联验证;@ConvertGroup
的作用是当验证o的分组是First时,那么验证o的分组是Second,即分组验证的转换。
假设我们需要显示如:用户名[NAME]
长度必须在[MIN]
到[MAX]
之间,此处大家可以看到,我们不想把一些数据写死,如NAME
、MIN
、MAX
;此时我们可以使用EL表达式。
- @Length(min = 5, max = 20, message = "{user.name.length.illegal}", groups = {First.class})
错误消息:
- user.name.length.illegal=用户名长度必须在{min}到{max}之间
其中我们可以使用{验证注解的属性}
得到这些值;如{min}
得到@Length
中的min
值;其他的也是类似的。
到此,我们还是无法得到出错的那个输入值,如name=zhangsan
。此时就需要EL表达式的支持,首先确定引入EL jar包且版本正确。
有时候默认的规则可能还不够,有时候还需要自定义规则,比如屏蔽关键词验证是非常常见的一个功能,比如在发帖时帖子中不允许出现admin等关键词。
- @Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE })
- @Retention(RUNTIME)
- //指定验证器
- @Constraint(validatedBy = ForbiddenValidator.class)
- @Documented
- public @interface Forbidden {
-
- //默认错误消息
- String message() default "{forbidden.word}";
-
- //分组
- Class<?>[] groups() default { };
-
- //负载
- Class<? extends Payload>[] payload() default { };
-
- //指定多个时使用
- @Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE })
- @Retention(RUNTIME)
- @Documented
- @interface List {
- Forbidden[] value();
- }
- }
- public class ForbiddenValidator implements ConstraintValidator<Forbidden, String> {
-
- private String[] forbiddenWords = {"admin"};
-
- @Override
- public void initialize(Forbidden constraintAnnotation) {
- //初始化,得到注解数据
- }
-
- @Override
- public boolean isValid(String value, ConstraintValidatorContext context) {
- if(StringUtils.isEmpty(value)) {
- return true;
- }
-
- for(String word : forbiddenWords) {
- if(value.contains(word)) {
- return false;//验证失败
- }
- }
- return true;
- }
- }