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. }  

     测试全部通过的

  • 相关阅读:
    HDU 1058 Humble Numbers
    HDU 1421 搬寝室
    HDU 1176 免费馅饼
    七种排序算法的实现和总结
    算法纲要
    UVa401 回文词
    UVa 10361 Automatic Poetry
    UVa 537 Artificial Intelligence?
    UVa 409 Excuses, Excuses!
    UVa 10878 Decode the tape
  • 原文地址:https://www.cnblogs.com/fanguangdexiaoyuer/p/6378162.html
Copyright © 2011-2022 走看看