Spring4新特性——泛型限定式依赖注入

Spring4新特性——核心容器的其他改进

Spring4新特性——Web开发的增强

Spring4新特性——集成Bean Validation 1.1(JSR-349)到SpringMVC

 

在之前的《跟我学SpringMVC》中的《第七章 注解式控制器的数据验证、类型转换及格式化》中已经介绍过SpringMVC集成Bean Validation 1.0(JSR-303),目前Bean Validation最新版本是Bean Validation 1.1(JSR-349),新特性可以到官网查看,笔者最喜欢的两个特性是:跨参数验证(比如密码和确认密码的验证)和支持在消息中使用EL表达式,其他的还有如方法参数/返回值验证、CDI和依赖注入、分组转换等。对于方法参数/返回值验证,大家可以参阅《Spring3.1 对Bean Validation规范的新支持(方法级别验证) 》。

 

Bean Validation 1.1当前实现是Hibernate validator 5,且spring4才支持。接下来我们从以下几个方法讲解Bean Validation 1.1,当然不一定是新特性:

  1.  集成Bean Validation 1.1到SpringMVC
  2.  分组验证及分组顺序
  3.  消息中使用EL表达式
  4.  方法参数/返回值验证
  5.  自定义验证规则
  6.  类级别验证器
  7.  脚本验证器
  8.  cross-parameter,跨参数验证
  9. 混合类级别验证器和跨参数验证器

因为大多数时候验证都配合web框架使用,而且很多朋友都咨询过如分组/跨参数验证,所以本文介绍下这些,且是和SpringMVC框架集成的例子,其他使用方式(比如集成到JPA中)可以参考其官方文档:

规范:http://beanvalidation.org/1.1/spec/

hibernate validator文档:http://hibernate.org/validator/ 

 

 1、集成Bean Validation 1.1到SpringMVC

1.1、项目搭建

首先添加hibernate validator 5依赖:



查看复制到剪贴板打印


1. <dependency>  
2.     <groupId>org.hibernate</groupId>  
3.     <artifactId>hibernate-validator</artifactId>  
4. 5.0.2.Final</version>  
5. </dependency>


如果想在消息中使用EL表达式,请确保EL表达式版本是 2.2或以上,如使用Tomcat6,请到Tomcat7中拷贝相应的EL jar包到Tomcat6中。



查看复制到剪贴板打印



    1. <dependency>  
    2.     <groupId>javax.el</groupId>  
    3.     <artifactId>javax.el-api</artifactId>  
    4. 2.2.4</version>  
    5.     <scope>provided</scope>  
    6. </dependency>


    请确保您使用的Web容器有相应版本的el jar包。

     

    对于其他POM依赖请下载附件中的项目参考。

     

    1.2、Spring MVC配置文件(spring-mvc.xml):



    查看复制到剪贴板打印



      1. <!-- 指定自己定义的validator -->  
      2. <mvc:annotation-driven validator="validator"/>  
      3.   
      4. <!-- 以下 validator  ConversionService 在使用 mvc:annotation-driven 会 自动注册-->  
      5. <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">  
      6. "providerClass" value="org.hibernate.validator.HibernateValidator"/>  
      7.     <!-- 如果不加默认到 使用classpath下的 ValidationMessages.properties -->  
      8. "validationMessageSource" ref="messageSource"/>  
      9. </bean>  
      10.   
      11. <!-- 国际化的消息资源文件(本系统中主要用于显示/错误消息定制) -->  
      12. <bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">  
      13. "basenames">  
      14.         <list>  
      15.             <!-- 在web环境中一定要定位到classpath 否则默认到当前web应用下找  -->  
      16.             <value>classpath:messages</value>  
      17.             <value>classpath:org/hibernate/validator/ValidationMessages</value>  
      18.         </list>  
      19.     </property>  
      20. "useCodeAsDefaultMessage" value="false"/>  
      21. "defaultEncoding" value="UTF-8"/>  
      22. "cacheSeconds" value="60"/>  
      23. </bean>



      此处主要把bean validation的消息查找委托给spring的messageSource。

       

      1.3、实体验证注解:



      查看复制到剪贴板打印


      1. public class User implements Serializable {  
      2. @NotNull(message = "{user.id.null}")  
      3. private Long id;  
      4.   
      5. @NotEmpty(message = "{user.name.null}")  
      6. @Length(min = 5, max = 20, message = "{user.name.length.illegal}")  
      7. @Pattern(regexp = "[a-zA-Z]{5,20}", message = "{user.name.illegal}")  
      8. private String name;  
      9.   
      10. @NotNull(message = "{user.password.null}")  
      11. private String password;  
      12. }

       



      对于验证规则可以参考官方文档,或者《第七章 注解式控制器的数据验证、类型转换及格式化》。

       

      1.4、错误消息文件messages.properties:



      查看复制到剪贴板打印

      1. user.id.null=用户编号不能为空  
      2. user.name.null=用户名不能为空  
      3. user.name.length.illegal=用户名长度必须在5到20之间  
      4. user.name.illegal=用户名必须是字母  
      5. user.password.null=密码不能为空


       

      1.5、控制器



      查看复制到剪贴板打印


      1. @Controller  
      2. public class UserController {  
      3.   
      4. @RequestMapping("/save")  
      5. public String save(@Valid User user, BindingResult result) {  
      6. if(result.hasErrors()) {  
      7. return "error";  
      8.         }  
      9. return "success";  
      10.     }  
      11. }

      1.6、错误页面:



      查看复制到剪贴板打印



        1. <spring:hasBindErrors name="user">  
        2. if test="${errors.fieldErrorCount > 0}">  
        3.         字段错误:<br/>  
        4. "${errors.fieldErrors}" var="error">  
        5. "message" code="${error.code}" arguments="${error.arguments}" text="${error.defaultMessage}"/>  
        6.             ${error.field}------${message}<br/>  
        7.         </c:forEach>  
        8. if>  
        9.   
        10. if test="${errors.globalErrorCount > 0}">  
        11.         全局错误:<br/>  
        12. "${errors.globalErrors}" var="error">  
        13. "message" code="${error.code}" arguments="${error.arguments}" text="${error.defaultMessage}"/>  
        14. if test="${not empty message}">  
        15.                 ${message}<br/>  
        16. if>  
        17.         </c:forEach>  
        18. if>  
        19. </spring:hasBindErrors>

        大家以后可以根据这个做通用的错误消息显示规则。比如我前端页面使用validationEngine显示错误消息,那么我可以定义一个tag来通用化错误消息的显示:showFieldError.tag。  

         

        1.7、测试

        输入如:http://localhost:9080/spring4/save?name=123 , 我们得到如下错误:



        查看复制到剪贴板打印


        1. name------用户名必须是字母  
        2. name------用户名长度必须在5到20之间  
        3. password------密码不能为空  
        4. id------用户编号不能为空



         

        基本的集成就完成了。

         

        如上测试有几个小问题:

        1、错误消息顺序,大家可以看到name的错误消息顺序不是按照书写顺序的,即不确定;

        2、我想显示如:用户名【zhangsan】必须在5到20之间;其中我们想动态显示:用户名、min,max;而不是写死了;

        3、我想在修改的时候只验证用户名,其他的不验证怎么办。

        接下来我们挨着试试吧。

         

        2、分组验证及分组顺序

        如果我们想在新增的情况验证id和name,而修改的情况验证name和password,怎么办? 那么就需要分组了。

        首先定义分组接口:



        查看复制到剪贴板打印

        1. public interface First {  
        2. }  
        3.   
        4. public interface Second {  
        5. }


        分组接口就是两个普通的接口,用于标识,类似于java.io.Serializable。

         

        接着我们使用分组接口标识实体:



        查看复制到剪贴板打印


        1. public class User implements Serializable {  
        2.   
        3. @NotNull(message = "{user.id.null}", groups = {First.class})  
        4. private Long id;  
        5.   
        6. @Length(min = 5, max = 20, message = "{user.name.length.illegal}", groups = {Second.class})  
        7. @Pattern(regexp = "[a-zA-Z]{5,20}", message = "{user.name.illegal}", groups = {Second.class})  
        8. private String name;  
        9.   
        10. @NotNull(message = "{user.password.null}", groups = {First.class, Second.class})  
        11. private String password;  
        12. }


         

        验证时使用如:



        查看复制到剪贴板打印


        1. @RequestMapping("/save")  
        2. public String save(@Validated({Second.class}) User user, BindingResult result) {  
        3. if(result.hasErrors()) {  
        4. return "error";  
        5.     }  
        6. return "success";  
        7. }


        即通过@Validate注解标识要验证的分组;如果要验证两个的话,可以这样@Validated({First.class, Second.class})。

         

        接下来我们来看看通过分组来指定顺序;还记得之前的错误消息吗? user.name会显示两个错误消息,而且顺序不确定;如果我们先验证一个消息;如果不通过再验证另一个怎么办?可以通过@GroupSequence指定分组验证顺序:

         



        查看复制到剪贴板打印



          1. @GroupSequence({First.class, Second.class, User.class})  
          2. public class User implements Serializable {  
          3. private Long id;  
          4.   
          5. @Length(min = 5, max = 20, message = "{user.name.length.illegal}", groups = {First.class})  
          6. @Pattern(regexp = "[a-zA-Z]{5,20}", message = "{user.name.illegal}", groups = {Second.class})  
          7. private String name;  
          8.       
          9. private String password;  
          10. }



          通过@GroupSequence指定验证顺序:先验证First分组,如果有错误立即返回而不会验证Second分组,接着如果First分组验证通过了,那么才去验证Second分组,最后指定User.class表示那些没有分组的在最后。这样我们就可以实现按顺序验证分组了。

           

          3、消息中使用EL表达式

          假设我们需要显示如:用户名[NAME]长度必须在[MIN]到[MAX]之间,此处大家可以看到,我们不想把一些数据写死,如NAME、MIN、MAX;此时我们可以使用EL表达式。

           

          如:



          查看复制到剪贴板打印


          1. @Length(min = 5, max = 20, message = "{user.name.length.illegal}", groups = {First.class})

          错误消息:



          查看复制到剪贴板打印



            1. user.name.length.illegal=用户名长度必须在{min}到{max}之间



             

            其中我们可以使用{验证注解的属性}得到这些值;如{min}得到@Length中的min值;其他的也是类似的。

             

            到此,我们还是无法得到出错的那个输入值,如name=zhangsan。此时就需要EL表达式的支持,首先确定引入EL jar包且版本正确。然后使用如:



            查看复制到剪贴板打印

            1. user.name.length.illegal=用户名[${validatedValue}]长度必须在5到20之间

            使用如EL表达式:${validatedValue}得到输入的值,如zhangsan。当然我们还可以使用如${min > 1 ? '大于1' : '小于等于1'},及在EL表达式中也能拿到如@Length的min等数据。

             

            另外我们还可以拿到一个java.util.Formatter类型的formatter变量进行格式化:



            查看复制到剪贴板打印



              1. ${formatter.format("%04d", min)}



               

              4、方法参数/返回值验证

              这个可以参考《Spring3.1 对Bean Validation规范的新支持(方法级别验证) 》,概念是类似的,具体可以参考Bean Validation 文档。

               

              5、自定义验证规则

              有时候默认的规则可能还不够,有时候还需要自定义规则,比如屏蔽关键词验证是非常常见的一个功能,比如在发帖时帖子中不允许出现admin等关键词。

               

              1、定义验证注解



              查看复制到剪贴板打印


              1. package com.sishuok.spring4.validator;  
              2.   
              3. import javax.validation.Constraint;  
              4. import javax.validation.Payload;  
              5. import java.lang.annotation.Documented;  
              6. import java.lang.annotation.Retention;  
              7. import java.lang.annotation.Target;  
              8. import static java.lang.annotation.ElementType.*;  
              9. import static java.lang.annotation.RetentionPolicy.*;  
              10. /** 
              11.  * <p>User: Zhang Kaitao 
              12.  * <p>Date: 13-12-15 
              13.  * <p>Version: 1.0 
              14.  */  
              15.   
              16. @Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE })  
              17. @Retention(RUNTIME)  
              18. //指定验证器  
              19. @Constraint(validatedBy = ForbiddenValidator.class)  
              20. @Documented  
              21. public @interface Forbidden {  
              22.   
              23. //默认错误消息  
              24. default "{forbidden.word}";  
              25.   
              26. //分组  
              27. default { };  
              28.   
              29. //负载  
              30. extends Payload>[] payload() default { };  
              31.   
              32. //指定多个时使用  
              33. @Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE })  
              34. @Retention(RUNTIME)  
              35. @Documented  
              36. @interface List {  
              37.         Forbidden[] value();  
              38.     }  
              39. }


               

              2、 定义验证器



              查看复制到剪贴板打印


              1. package com.sishuok.spring4.validator;  
              2.   
              3. import org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorContextImpl;  
              4. import org.springframework.beans.factory.annotation.Autowired;  
              5. import org.springframework.context.ApplicationContext;  
              6. import org.springframework.util.StringUtils;  
              7.   
              8. import javax.validation.ConstraintValidator;  
              9. import javax.validation.ConstraintValidatorContext;  
              10. import java.io.Serializable;  
              11.   
              12. /** 
              13.  * <p>User: Zhang Kaitao 
              14.  * <p>Date: 13-12-15 
              15.  * <p>Version: 1.0 
              16.  */  
              17. public class ForbiddenValidator implements ConstraintValidator<Forbidden, String> {  
              18.   
              19. private String[] forbiddenWords = {"admin"};  
              20.   
              21. @Override  
              22. public void initialize(Forbidden constraintAnnotation) {  
              23. //初始化,得到注解数据  
              24.     }  
              25.   
              26. @Override  
              27. public boolean isValid(String value, ConstraintValidatorContext context) {  
              28. if(StringUtils.isEmpty(value)) {  
              29. return true;  
              30.         }  
              31.   
              32. for(String word : forbiddenWords) {  
              33. if(value.contains(word)) {  
              34. return false;//验证失败  
              35.             }  
              36.         }  
              37. return true;  
              38.     }  
              39. }



               验证器中可以使用spring的依赖注入,如注入:@Autowired  private ApplicationContext ctx; 

               

              3、使用



              查看复制到剪贴板打印


              1. public class User implements Serializable {  
              2. @Forbidden()  
              3. private String name;  
              4. }


               

              4、当我们在提交name中含有admin的时候会输出错误消息:



              查看复制到剪贴板打印



              1. forbidden.word=您输入的数据中有非法关键词  



               

              问题来了,哪个词是非法的呢?bean validation 和 hibernate validator都没有提供相应的api提供这个数据,怎么办呢?通过跟踪代码,发现一种不是特别好的方法:我们可以覆盖org.hibernate.validator.internal.metadata.descriptor.ConstraintDescriptorImpl实现(即复制一份代码放到我们的src中),然后覆盖buildAnnotationParameterMap方法;



              查看复制到剪贴板打印



                1. private Map<String, Object> buildAnnotationParameterMap(Annotation annotation) {  
                2.     ……  
                3. //将Collections.unmodifiableMap( parameters );替换为如下语句  
                4. return parameters;  
                5. }



                 即允许这个数据可以修改;然后在ForbiddenValidator中:



                查看复制到剪贴板打印


                1. for(String word : forbiddenWords) {  
                2. if(value.contains(word)) {  
                3. "word", word);  
                4. return false;//验证失败  
                5.     }  
                6. }


                通过((ConstraintValidatorContextImpl)context).getConstraintDescriptor().getAttributes().put("word", word);添加自己的属性;放到attributes中的数据可以通过${} 获取。然后消息就可以变成:


                查看复制到剪贴板打印


                1. forbidden.word=您输入的数据中有非法关键词【{word}】


                这种方式不是很友好,但是可以解决我们的问题。

                 

                典型的如密码、确认密码的场景,非常常用;如果没有这个功能我们需要自己写代码来完成;而且经常重复自己。接下来看看bean validation 1.1如何实现的。

                 

                6、类级别验证器

                6.1、定义验证注解



                查看复制到剪贴板打印


                1. package com.sishuok.spring4.validator;  
                2.   
                3. import javax.validation.Constraint;  
                4. import javax.validation.Payload;  
                5. import javax.validation.constraints.NotNull;  
                6. import java.lang.annotation.Documented;  
                7. import java.lang.annotation.Retention;  
                8. import java.lang.annotation.Target;  
                9. import static java.lang.annotation.ElementType.*;  
                10. import static java.lang.annotation.RetentionPolicy.*;  
                11. /** 
                12.  * <p>User: Zhang Kaitao 
                13.  * <p>Date: 13-12-15 
                14.  * <p>Version: 1.0 
                15.  */  
                16.   
                17. @Target({ TYPE, ANNOTATION_TYPE})  
                18. @Retention(RUNTIME)  
                19. //指定验证器  
                20. @Constraint(validatedBy = CheckPasswordValidator.class)  
                21. @Documented  
                22. public @interface CheckPassword {  
                23.   
                24. //默认错误消息  
                25. default "";  
                26.   
                27. //分组  
                28. default { };  
                29.   
                30. //负载  
                31. extends Payload>[] payload() default { };  
                32.   
                33. //指定多个时使用  
                34. @Target({ FIELD, METHOD, PARAMETER, ANNOTATION_TYPE })  
                35. @Retention(RUNTIME)  
                36. @Documented  
                37. @interface List {  
                38.         CheckPassword[] value();  
                39.     }  
                40. }


                6.2、 定义验证器



                查看复制到剪贴板打印



                  1. package com.sishuok.spring4.validator;  
                  2.   
                  3. import com.sishuok.spring4.entity.User;  
                  4. import org.springframework.util.StringUtils;  
                  5.   
                  6. import javax.validation.ConstraintValidator;  
                  7. import javax.validation.ConstraintValidatorContext;  
                  8.   
                  9. /** 
                  10.  * <p>User: Zhang Kaitao 
                  11.  * <p>Date: 13-12-15 
                  12.  * <p>Version: 1.0 
                  13.  */  
                  14. public class CheckPasswordValidator implements ConstraintValidator<CheckPassword, User> {  
                  15.   
                  16. @Override  
                  17. public void initialize(CheckPassword constraintAnnotation) {  
                  18.     }  
                  19.   
                  20. @Override  
                  21. public boolean isValid(User user, ConstraintValidatorContext context) {  
                  22. if(user == null) {  
                  23. return true;  
                  24.         }  
                  25.   
                  26. //没有填密码  
                  27. if(!StringUtils.hasText(user.getPassword())) {  
                  28.             context.disableDefaultConstraintViolation();  
                  29. "{password.null}")  
                  30. "password")  
                  31.                     .addConstraintViolation();  
                  32. return false;  
                  33.         }  
                  34.   
                  35. if(!StringUtils.hasText(user.getConfirmation())) {  
                  36.             context.disableDefaultConstraintViolation();  
                  37. "{password.confirmation.null}")  
                  38. "confirmation")  
                  39.                     .addConstraintViolation();  
                  40. return false;  
                  41.         }  
                  42.   
                  43. //两次密码不一样  
                  44. if (!user.getPassword().trim().equals(user.getConfirmation().trim())) {  
                  45.             context.disableDefaultConstraintViolation();  
                  46. "{password.confirmation.error}")  
                  47. "confirmation")  
                  48.                     .addConstraintViolation();  
                  49. return false;  
                  50.         }  
                  51. return true;  
                  52.     }  
                  53. }



                  其中我们通过disableDefaultConstraintViolation禁用默认的约束;然后通过buildConstraintViolationWithTemplate(消息模板)/addPropertyNode(所属属性)/addConstraintViolation定义我们自己的约束。

                   

                  6.3、使用



                  查看复制到剪贴板打印

                  1. @CheckPassword()  
                  2. public class User implements Serializable {  
                  3. }


                   放到类头上即可。

                   

                  7、通过脚本验证



                  查看复制到剪贴板打印



                    1. @ScriptAssert(script = "_this.password==_this.confirmation", lang = "javascript", alias = "_this", message = "{password.confirmation.error}")  
                    2. public class User implements Serializable {  
                    3. }



                    通过脚本验证是非常简单而且强大的,lang指定脚本语言(请参考javax.script.ScriptEngineManager JSR-223),alias是在脚本验证中User对象的名字,但是大家会发现一个问题:错误消息怎么显示呢? 在springmvc 中会添加到全局错误消息中,这肯定不是我们想要的,我们改造下吧。

                     

                    7.1、定义验证注解



                    查看复制到剪贴板打印


                    1. package com.sishuok.spring4.validator;  
                    2.   
                    3. import org.hibernate.validator.internal.constraintvalidators.ScriptAssertValidator;  
                    4.   
                    5. import java.lang.annotation.Documented;  
                    6. import java.lang.annotation.Retention;  
                    7. import java.lang.annotation.Target;  
                    8. import javax.validation.Constraint;  
                    9. import javax.validation.Payload;  
                    10.   
                    11. import static java.lang.annotation.ElementType.TYPE;  
                    12. import static java.lang.annotation.RetentionPolicy.RUNTIME;  
                    13.   
                    14. @Target({ TYPE })  
                    15. @Retention(RUNTIME)  
                    16. @Constraint(validatedBy = {PropertyScriptAssertValidator.class})  
                    17. @Documented  
                    18. public @interface PropertyScriptAssert {  
                    19.   
                    20. default "{org.hibernate.validator.constraints.ScriptAssert.message}";  
                    21.   
                    22. default { };  
                    23.   
                    24. extends Payload>[] payload() default { };  
                    25.   
                    26.     String lang();  
                    27.   
                    28.     String script();  
                    29.   
                    30. default "_this";  
                    31.   
                    32.     String property();  
                    33.   
                    34. @Target({ TYPE })  
                    35. @Retention(RUNTIME)  
                    36. @Documented  
                    37. public @interface List {  
                    38.         PropertyScriptAssert[] value();  
                    39.     }  
                    40. }


                    和ScriptAssert没什么区别,只是多了个property用来指定出错后给实体的哪个属性。

                     

                    7.2、验证器



                    查看复制到剪贴板打印


                    1. package com.sishuok.spring4.validator;  
                    2.   
                    3. import javax.script.ScriptException;  
                    4. import javax.validation.ConstraintDeclarationException;  
                    5. import javax.validation.ConstraintValidator;  
                    6. import javax.validation.ConstraintValidatorContext;  
                    7.   
                    8. import com.sishuok.spring4.validator.PropertyScriptAssert;  
                    9. import org.hibernate.validator.constraints.ScriptAssert;  
                    10. import org.hibernate.validator.internal.util.Contracts;  
                    11. import org.hibernate.validator.internal.util.logging.Log;  
                    12. import org.hibernate.validator.internal.util.logging.LoggerFactory;  
                    13. import org.hibernate.validator.internal.util.scriptengine.ScriptEvaluator;  
                    14. import org.hibernate.validator.internal.util.scriptengine.ScriptEvaluatorFactory;  
                    15.   
                    16. import static org.hibernate.validator.internal.util.logging.Messages.MESSAGES;  
                    17.   
                    18. public class PropertyScriptAssertValidator implements ConstraintValidator<PropertyScriptAssert, Object> {  
                    19.   
                    20. private static final Log log = LoggerFactory.make();  
                    21.   
                    22. private String script;  
                    23. private String languageName;  
                    24. private String alias;  
                    25. private String property;  
                    26. private String message;  
                    27.   
                    28. public void initialize(PropertyScriptAssert constraintAnnotation) {  
                    29.         validateParameters( constraintAnnotation );  
                    30.   
                    31. this.script = constraintAnnotation.script();  
                    32. this.languageName = constraintAnnotation.lang();  
                    33. this.alias = constraintAnnotation.alias();  
                    34. this.property = constraintAnnotation.property();  
                    35. this.message = constraintAnnotation.message();  
                    36.     }  
                    37.   
                    38. public boolean isValid(Object value, ConstraintValidatorContext constraintValidatorContext) {  
                    39.   
                    40.         Object evaluationResult;  
                    41.         ScriptEvaluator scriptEvaluator;  
                    42.   
                    43. try {  
                    44.             ScriptEvaluatorFactory evaluatorFactory = ScriptEvaluatorFactory.getInstance();  
                    45.             scriptEvaluator = evaluatorFactory.getScriptEvaluatorByLanguageName( languageName );  
                    46.         }  
                    47. catch ( ScriptException e ) {  
                    48. throw new ConstraintDeclarationException( e );  
                    49.         }  
                    50.   
                    51. try {  
                    52.             evaluationResult = scriptEvaluator.evaluate( script, value, alias );  
                    53.         }  
                    54. catch ( ScriptException e ) {  
                    55. throw log.getErrorDuringScriptExecutionException( script, e );  
                    56.         }  
                    57.   
                    58. if ( evaluationResult == null ) {  
                    59. throw log.getScriptMustReturnTrueOrFalseException( script );  
                    60.         }  
                    61. if ( !( evaluationResult instanceof Boolean ) ) {  
                    62. throw log.getScriptMustReturnTrueOrFalseException(  
                    63.                     script,  
                    64.                     evaluationResult,  
                    65.                     evaluationResult.getClass().getCanonicalName()  
                    66.             );  
                    67.         }  
                    68.   
                    69. if(Boolean.FALSE.equals(evaluationResult)) {  
                    70.             constraintValidatorContext.disableDefaultConstraintViolation();  
                    71.             constraintValidatorContext  
                    72.                     .buildConstraintViolationWithTemplate(message)  
                    73.                     .addPropertyNode(property)  
                    74.                     .addConstraintViolation();  
                    75.         }  
                    76.   
                    77. return Boolean.TRUE.equals( evaluationResult );  
                    78.     }  
                    79.   
                    80. private void validateParameters(PropertyScriptAssert constraintAnnotation) {  
                    81. "script" ) );  
                    82. "lang" ) );  
                    83. "alias" ) );  
                    84. "property" ) );  
                    85. "message" ) );  
                    86.     }  
                    87. }


                    和之前的类级别验证器类似,就不多解释了,其他代码全部拷贝自org.hibernate.validator.internal.constraintvalidators.ScriptAssertValidator。

                     

                    7.3、使用



                    查看复制到剪贴板打印


                    1. @PropertyScriptAssert(property = "confirmation", script = "_this.password==_this.confirmation", lang = "javascript", alias = "_this", message = "{password.confirmation.error}")


                    和之前的区别就是多了个property,用来指定出错时给哪个字段。 这个相对之前的类级别验证器更通用一点。

                     

                    8、cross-parameter,跨参数验证

                    直接看示例;

                     

                    8.1、首先注册MethodValidationPostProcessor,起作用请参考《Spring3.1 对Bean Validation规范的新支持(方法级别验证) 》 



                    查看复制到剪贴板打印


                    1. <bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor">  
                    2. "validator" ref="validator"/>  
                    3. </bean>

                     

                    8.2、Service 



                    查看复制到剪贴板打印



                      1. @Validated  
                      2. @Service  
                      3. public class UserService {  
                      4.   
                      5. @CrossParameter  
                      6. public void changePassword(String password, String confirmation) {  
                      7.   
                      8.     }  
                      9. }



                      通过@Validated注解UserService表示该类中有需要进行方法参数/返回值验证;   @CrossParameter注解方法表示要进行跨参数验证;即验证password和confirmation是否相等。

                       

                      8.3、验证注解 



                      查看复制到剪贴板打印



                      1. package com.sishuok.spring4.validator;  
                      2.   
                      3. //省略import  
                      4.   
                      5. @Constraint(validatedBy = CrossParameterValidator.class)  
                      6. @Target({ METHOD, CONSTRUCTOR, ANNOTATION_TYPE })  
                      7. @Retention(RUNTIME)  
                      8. @Documented  
                      9. public @interface CrossParameter {  
                      10.   
                      11. default "{password.confirmation.error}";  
                      12. default { };  
                      13. extends Payload>[] payload() default { };  
                      14.   
                      15. }



                       

                      8.4、验证器 



                      查看复制到剪贴板打印


                      1. package com.sishuok.spring4.validator;  
                      2.   
                      3. //省略import  
                      4.   
                      5. @SupportedValidationTarget(ValidationTarget.PARAMETERS)  
                      6. public class CrossParameterValidator implements ConstraintValidator<CrossParameter, Object[]> {  
                      7.   
                      8. @Override  
                      9. public void initialize(CrossParameter constraintAnnotation) {  
                      10.     }  
                      11.   
                      12. @Override  
                      13. public boolean isValid(Object[] value, ConstraintValidatorContext context) {  
                      14. if(value == null || value.length != 2) {  
                      15. throw new IllegalArgumentException("must have two args");  
                      16.         }  
                      17. if(value[0] == null || value[1] == null) {  
                      18. return true;  
                      19.         }  
                      20. if(value[0].equals(value[1])) {  
                      21. return true;  
                      22.         }  
                      23. return false;  
                      24.     }  
                      25. }



                      其中@SupportedValidationTarget(ValidationTarget.PARAMETERS)表示验证参数; value将是参数列表。 

                       

                      8.5、使用



                      查看复制到剪贴板打印



                        1. @RequestMapping("/changePassword")  
                        2. public String changePassword(  
                        3. @RequestParam("password") String password,  
                        4. @RequestParam("confirmation") String confirmation, Model model) {  
                        5. try {  
                        6.         userService.changePassword(password, confirmation);  
                        7. catch (ConstraintViolationException e) {  
                        8. for(ConstraintViolation violation : e.getConstraintViolations()) {  
                        9.             System.out.println(violation.getMessage());  
                        10.         }  
                        11.     }  
                        12. return "success";  
                        13. }



                        调用userService.changePassword方法,如果验证失败将抛出ConstraintViolationException异常,然后得到ConstraintViolation,调用getMessage即可得到错误消息;然后到前台显示即可。

                         

                        从以上来看,不如之前的使用方便,需要自己对错误消息进行处理。 下一节我们也写个脚本方式的跨参数验证器。

                         

                        9、混合类级别验证器和跨参数验证器

                        9.1、验证注解



                        查看复制到剪贴板打印



                          1. package com.sishuok.spring4.validator;  
                          2.   
                          3. //省略import  
                          4.   
                          5. @Constraint(validatedBy = {  
                          6. class,  
                          7. class  
                          8. })  
                          9. @Target({ TYPE, FIELD, PARAMETER, METHOD, CONSTRUCTOR, ANNOTATION_TYPE })  
                          10. @Retention(RUNTIME)  
                          11. @Documented  
                          12. public @interface CrossParameterScriptAssert {  
                          13. default "error";  
                          14. default { };  
                          15. extends Payload>[] payload() default { };  
                          16.     String script();  
                          17.     String lang();  
                          18. default "_this";  
                          19. default "";  
                          20. default ConstraintTarget.IMPLICIT;  
                          21. }



                           

                          此处我们通过@Constraint指定了两个验证器,一个类级别的,一个跨参数的。validationAppliesTo指定为ConstraintTarget.IMPLICIT,表示隐式自动判断。

                           

                          9.2、验证器

                          请下载源码查看

                           

                          9.3、使用

                          9.3.1、类级别使用



                          查看复制到剪贴板打印


                          1. @CrossParameterScriptAssert(property = "confirmation", script = "_this.password==_this.confirmation", lang = "javascript", alias = "_this", message = "{password.confirmation.error}")


                          指定property即可,其他和之前的一样。

                          9.3.2、跨参数验证



                          查看复制到剪贴板打印



                            1. @CrossParameterScriptAssert(script = "args[0] == args[1]", lang = "javascript", alias = "args", message = "{password.confirmation.error}")  
                            2. public void changePassword(String password, String confirmation) {  
                            3.   
                            4. }


                            通过args[0]==args[1] 来判断是否相等。

                             

                            这样,我们的验证注解就自动适应两种验证规则了。  

                             

                             

                             

                            到此,我们已经完成大部分Bean Validation的功能实验了。对于如XML配置、编程式验证API的使用等对于我们使用SpringMVC这种web环境用处不大,所以就不多介绍了,有兴趣可以自己下载官方文档学习。