zoukankan      html  css  js  c++  java
  • 创建自定义JSR303的验证约束(Creating custom constraints)

    转载:http://clongjava.iteye.com/blog/1317649

    由于输入验证在软件开发中是必须的一件事情,特别是与用户交互的软件产品,验证用户的潜在输入错误是必不可少的一件事情,然而各种开源的验证框架也很多,为了一统标准,jsr303规范横空出世了,它定义了一些标准的验证约束,标准毕竟是标准,它不可能定义到所有的验证约束,它只是提供了一些基本的常用的约束,不过它提供了一个可拓展的自定义验证约束。下面就来说说怎么样自定义一个约束.

          为了创建一个自定义约束,以下三个步骤是必须的。
    • Create a constraint annotation (首先定义一个约束注解)
    • Implement a validator(第二步是实现这个验证器)
    • Define a default error message(最后添加一条默认的错误消息即可)

        假定有这么一个要求,要验证用户的两次输入密码必须是相同的,非常常见的一个要求。下面就基于这个要求来自定义一个约束。

    Java代码  收藏代码
    1. package org.leochen.samples;  
    2.   
    3. import javax.validation.Constraint;  
    4. import javax.validation.Payload;  
    5. import java.lang.annotation.*;  
    6.   
    7. /** 
    8.  * User: leochen 
    9.  * Date: 11-12-8 
    10.  * Time: 下午11:31 
    11.  */  
    12.   
    13. @Target({ElementType.TYPE,ElementType.ANNOTATION_TYPE})  
    14. @Retention(RetentionPolicy.RUNTIME)  
    15. @Constraint(validatedBy = MatchesValidator.class)  
    16. @Documented  
    17. public @interface Matches {  
    18.     String message() default "{constraint.not.matches}";  
    19.     Class<?>[] groups() default {};  
    20.     Class<? extends Payload>[] payload() default {};  
    21.   
    22.     String field();  
    23.     String verifyField();  
    24. }  

     从上到下来说吧,@Target表示注解可出现在哪些地方,比如可以出现在class上,field,method,又或者是在另外一个annotation上,这里限制只能出现在类和另外一个注解上,@Retention表示该注解的保存范围是哪里,RUNTIME表示在源码(source)、编译好的.class文件中保留信息,在执行的时候会把这一些信息加载到JVM中去的.@Constraint比较重要,表示哪个验证器提供验证。@interface表明这是一个注解,和class一样都是关键字,message(),groups()和payload()这三个方法是一个标准的约束所具备的,其中message()是必须的,{constraint.not.matches}表示该消息是要插值计算的,也就是说是要到资源文件中寻找这个key的,如果不加{}就表示是一个普通的消息,直接文本显示,如果消息中有需要用到{或}符号的,需要进行转义,用{和}来表示。groups()表示该约束属于哪个验证组,在验证某个bean部分属性是特别有用(也说不清了,具体可以查看Hibernate Validator的文档细看) default必须是一个类型为Class<?>[]的空数组,attribute payload that can be used by clients of the Bean Validation API to assign custom payload objects to a constraint. This attribute is not used by the API itself.下面连个字段是我们添加进去的,表示要验证字段的名称,比如password和confirmPassword.

        下面就来实现这个约束。

    Java代码  收藏代码
    1. package org.leochen.samples;  
    2.   
    3. import org.apache.commons.beanutils.BeanUtils;  
    4.   
    5. import javax.validation.ConstraintValidator;  
    6. import javax.validation.ConstraintValidatorContext;  
    7. import java.lang.reflect.InvocationTargetException;  
    8.   
    9. /** 
    10.  * User: leochen 
    11.  * Date: 11-12-8 
    12.  * Time: 下午11:39 
    13.  */  
    14. public class MatchesValidator implements ConstraintValidator<Matches,Object>{  
    15.     private String field;  
    16.     private String verifyField;  
    17.   
    18.     public void initialize(Matches matches) {  
    19.         this.field = matches.field();  
    20.         this.verifyField = matches.verifyField();  
    21.     }  
    22.   
    23.     public boolean isValid(Object value, ConstraintValidatorContext context) {  
    24.         try {  
    25.             String fieldValue= BeanUtils.getProperty(value,field);  
    26.             String verifyFieldValue = BeanUtils.getProperty(value,verifyField);  
    27.             boolean valid = (fieldValue == null) && (verifyFieldValue == null);  
    28.             if(valid){  
    29.                 return true;  
    30.             }  
    31.   
    32.             boolean match = (fieldValue!=null) && fieldValue.equals(verifyFieldValue);  
    33.             if(!match){  
    34.                 String messageTemplate = context.getDefaultConstraintMessageTemplate();  
    35.                 context.disableDefaultConstraintViolation();  
    36.                 context.buildConstraintViolationWithTemplate(messageTemplate)  
    37.                         .addNode(verifyField)  
    38.                         .addConstraintViolation();  
    39.             }  
    40.             return match;  
    41.         } catch (IllegalAccessException e) {  
    42.             e.printStackTrace();  
    43.         } catch (InvocationTargetException e) {  
    44.             e.printStackTrace();  
    45.         } catch (NoSuchMethodException e) {  
    46.             e.printStackTrace();  
    47.         }  
    48.         return true;  
    49.     }  
    50. }  

     我们必须要实现ConstraintValidator这个接口,下面就来具体看看这个接口是怎么定义的吧:

    Java代码  收藏代码
    1. package javax.validation;  
    2.   
    3. import java.lang.annotation.Annotation;  
    4.   
    5. public interface ConstraintValidator<A extends Annotation, T> {  
    6.     /** 
    7.      * Initialize the validator in preparation for isValid calls. 
    8.      * The constraint annotation for a given constraint declaration 
    9.      * is passed. 
    10.      * <p/> 
    11.      * This method is guaranteed to be called before any use of this instance for 
    12.      * validation. 
    13.      * 
    14.      * @param constraintAnnotation annotation instance for a given constraint declaration 
    15.      */  
    16.     void initialize(A constraintAnnotation);  
    17.   
    18.     /** 
    19.      * Implement the validation logic. 
    20.      * The state of <code>value</code> must not be altered. 
    21.      * 
    22.      * This method can be accessed concurrently, thread-safety must be ensured 
    23.      * by the implementation. 
    24.      * 
    25.      * @param value object to validate 
    26.      * @param context context in which the constraint is evaluated 
    27.      * 
    28.      * @return false if <code>value</code> does not pass the constraint 
    29.      */  
    30.     boolean isValid(T value, ConstraintValidatorContext context);  
    31. }  

     A 表示边界范围为java.lang.annotation.Annotation即可,这个T参数必须满足下面两个限制条件:

    <!-- Generated by javadoc (build 1.6.0_20) on Fri Jun 04 05:41:40 PDT 2010 -->

    <noscript></noscript>

    • T must resolve to a non parameterized type (T 必须能被解析为非参数化的类型,通俗讲就是要能解析成具体类型,比如Object,Dog,Cat之类的,不能是一个占位符)
    • or generic parameters of T must be unbounded wildcard types(或者也可以是一个无边界范围含有通配符的泛型类型)

    我们在initialize (A  constraintAnnotation) 方法中获取到要验证的两个字段的名称,在isValid方法中编写验证规则。

    Java代码  收藏代码
    1. String fieldValue= BeanUtils.getProperty(value,field);  
    2. String verifyFieldValue = BeanUtils.getProperty(value,verifyField);  

     通过反射获取验证字段的值,由于我们要实现的是一个密码和确认密码一致的问题,而这两个字段类型都是java.lang.String类型,所以我们直接通过BeanUtils来获取他们各自的值。

    Java代码  收藏代码
    1. String messageTemplate = context.getDefaultConstraintMessageTemplate();  
    2.                 context.disableDefaultConstraintViolation();  
    3.                 context.buildConstraintViolationWithTemplate(messageTemplate)  
    4.                         .addNode(verifyField)  
    5.                         .addConstraintViolation();  

     以上是我们把验证出错的消息放在哪个字段上显示,一般我们是在确认密码上显示密码不一致的消息。

    好了这样我们的自定义约束就完成了,下面来使用并测试吧。

    假如我们要验证这么一个formbean

    Java代码  收藏代码
    1. package org.leochen.samples;  
    2.   
    3. /** 
    4.  * User: leochen 
    5.  * Date: 11-12-20 
    6.  * Time: 下午4:04 
    7.  */  
    8. @Matches(field = "password", verifyField = "confirmPassword",  
    9.                  message = "{constraint.confirmNewPassword.not.match.newPassword}")  
    10. public class TwoPasswords {  
    11.     private String password;  
    12.     private String confirmPassword;  
    13.   
    14.     public String getPassword() {  
    15.         return password;  
    16.     }  
    17.   
    18.     public void setPassword(String password) {  
    19.         this.password = password;  
    20.     }  
    21.   
    22.     public String getConfirmPassword() {  
    23.         return confirmPassword;  
    24.     }  
    25.   
    26.     public void setConfirmPassword(String confirmPassword) {  
    27.         this.confirmPassword = confirmPassword;  
    28.     }  
    29. }    

     在路径下放入我们的资源文件:ValidationMessages.properties(名字必须叫这个,不然你就费好大一番劲,何苦呢是不是,基于约定来)

    Java代码  收藏代码
    1. javax.validation.constraints.AssertFalse.message = must be false  
    2. javax.validation.constraints.AssertTrue.message  = must be true  
    3. javax.validation.constraints.DecimalMax.message  = must be less than or equal to {value}  
    4. javax.validation.constraints.DecimalMin.message  = must be greater than or equal to {value}  
    5. javax.validation.constraints.Digits.message      = numeric value out of bounds (<{integer} digits>.<{fraction} digits> expected)  
    6. javax.validation.constraints.Future.message      = must be in the future  
    7. javax.validation.constraints.Max.message         = must be less than or equal to {value}  
    8. javax.validation.constraints.Min.message         = must be greater than or equal to {value}  
    9. javax.validation.constraints.NotNull.message     = may not be null  
    10. javax.validation.constraints.Null.message        = must be null  
    11. javax.validation.constraints.Past.message        = must be in the past  
    12. javax.validation.constraints.Pattern.message     = must match "{regexp}"  
    13. javax.validation.constraints.Size.message        = size must be between {min} and {max}  
    14.   
    15. org.hibernate.validator.constraints.CreditCardNumber.message = invalid credit card number  
    16. org.hibernate.validator.constraints.Email.message            = not a well-formed email address  
    17. org.hibernate.validator.constraints.Length.message           = length must be between {min} and {max}  
    18. org.hibernate.validator.constraints.NotBlank.message         = may not be empty  
    19. org.hibernate.validator.constraints.NotEmpty.message         = may not be empty  
    20. org.hibernate.validator.constraints.Range.message            = must be between {min} and {max}  
    21. org.hibernate.validator.constraints.SafeHtml.message         = may have unsafe html content  
    22. org.hibernate.validator.constraints.ScriptAssert.message     = script expression "{script}" didn't evaluate to true  
    23. org.hibernate.validator.constraints.URL.message              = must be a valid URL  
    24.   
    25.   
    26.   
    27. ## custom constraints  
    28.   
    29. constraint.not.matches=two fields not matches  
    30. constraint.confirmNewPassword.not.match.newPassword=two password not the same  

     单元测试如下:

    Java代码  收藏代码
    1. package org.leochen.samples;  
    2.   
    3. import org.junit.BeforeClass;  
    4. import org.junit.Test;  
    5.   
    6. import javax.validation.ConstraintViolation;  
    7. import javax.validation.Validation;  
    8. import javax.validation.Validator;  
    9. import javax.validation.ValidatorFactory;  
    10.   
    11. import java.util.Set;  
    12.   
    13. import static junit.framework.Assert.assertEquals;  
    14. import static junit.framework.Assert.assertNotNull;  
    15.   
    16. /** 
    17.  * User: leochen 
    18.  * Date: 11-12-20 
    19.  * Time: 下午4:06 
    20.  */  
    21. public class TwoPasswordsTest {  
    22.     private static Validator validator;  
    23.   
    24.     @BeforeClass  
    25.     public static void setUp() {  
    26.         ValidatorFactory factory = Validation.buildDefaultValidatorFactory();  
    27.         validator = factory.getValidator();  
    28.     }  
    29.   
    30.   
    31.     @Test  
    32.     public void testBuildDefaultValidatorFactory() {  
    33.         ValidatorFactory factory = Validation.buildDefaultValidatorFactory();  
    34.         Validator validator = factory.getValidator();  
    35.   
    36.         assertNotNull(validator);  
    37.     }  
    38.   
    39.     @Test  
    40.     public void testPasswordEqualsConfirmPassword() {  
    41.         TwoPasswords bean = new TwoPasswords();  
    42.         bean.setPassword("110");  
    43.         bean.setConfirmPassword("110");  
    44.   
    45.         Set<ConstraintViolation<TwoPasswords>> constraintViolations = validator.validate(bean);  
    46.         for (ConstraintViolation<TwoPasswords> constraintViolation : constraintViolations) {  
    47.             System.out.println(constraintViolation.getMessage());  
    48.         }  
    49.   
    50.         assertEquals("newPassword and confirmNewPassword should be the same.", 0, constraintViolations.size());  
    51.     }  
    52.   
    53.     @Test  
    54.     public void testPasswordNotEqualsConfirmPassword() {  
    55.         TwoPasswords bean = new TwoPasswords();  
    56.         bean.setPassword("110");  
    57.         bean.setConfirmPassword("111");  
    58.   
    59.         Set<ConstraintViolation<TwoPasswords>> constraintViolations = validator.validate(bean);  
    60.   
    61.         assertEquals(1, constraintViolations.size());  
    62.         assertEquals("two password not the same", constraintViolations.iterator().next().getMessage());  
    63.     }  
    64.   
    65.     @Test  
    66.     public void testIfTwoPasswordWereNullShouldPast() {  
    67.         TwoPasswords bean = new TwoPasswords();  
    68.         bean.setPassword(null);  
    69.         bean.setConfirmPassword(null);  
    70.   
    71.         Set<ConstraintViolation<TwoPasswords>> constraintViolations = validator.validate(bean);  
    72.   
    73.         assertEquals(0, constraintViolations.size());  
    74.     }  
    75.   
    76.     @Test  
    77.     public void testIfOneIsNullAndOtherIsNotShouldNotPast() {  
    78.         TwoPasswords bean = new TwoPasswords();  
    79.         bean.setPassword(null);  
    80.         bean.setConfirmPassword("110");  
    81.   
    82.         Set<ConstraintViolation<TwoPasswords>> constraintViolations = validator.validate(bean);  
    83.   
    84.         assertEquals(1, constraintViolations.size());  
    85.         assertEquals("two password not the same", constraintViolations.iterator().next().getMessage());  
    86.     }  
    87. }  

     测试全部通过的

  • 相关阅读:
    「初级篇」跟我一起学docker(二)--核心概念和安装
    程序员有哪些借口可以让自己写出低质量的代码?
    「初级篇」跟我一起学docker(一)--认识
    后端程序猿怎么提高技术?提高编码质量?
    河南这么大的省,也所谓的准一线,为什么IT行业就是发展不起来呢?
    JAVA使用Gson解析json数据,实例
    JAVA equals 和 “==”的异同
    JAVA WEB 对返回数据进行按中文名称首字母A~Z升序排序
    JAVA验证数字的正则表达式,来一发
    【转】Java.util.ArrayList.set()方法实例
  • 原文地址:https://www.cnblogs.com/fanguangdexiaoyuer/p/6378162.html
Copyright © 2011-2022 走看看