关闭

创建自定义JSR303的验证约束

2794人阅读 评论(0) 收藏 举报
分类:
        由于输入验证在软件开发中是必须的一件事情,特别是与用户交互的软件产品,验证用户的潜在输入错误是必不可少的一件事情,然而各种开源的验证框架也很多,为了一统标准,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参数必须满足下面两个限制条件:

 

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

 测试全部通过的

0
0
查看评论

SpringMVC杂记(五) JSR303数据验证 自定义验证规则

http://yingzhuo.iteye.com/blog/1455438 SpringMVC杂记(五) JSR303数据验证 1) 首先JSR303的实现必须加入CLASSPATH Xml代码 dependency> group...
  • washli2001
  • washli2001
  • 2013-05-02 15:35
  • 2075

基于Spring MVC框架JSR-303的自定义注解Validator验证实现

服务器端的数据验证,对于一个WEB应用来说是非常重要的,而Spring从3.0开始支持JSR-303规范,它定义了一些标准的验证约束,同时也提供了一个可扩展的自定义方式来满足不同的开发需要,大象以SSM3为基础,结合实例来说明如何通过Spring MVC用自定义约束注解的方式来实现Validat...
  • singno_java
  • singno_java
  • 2014-06-21 13:22
  • 2185

SpringMVC 使用验证框架 Bean Validation(下)

本文接上一篇《SpringMVC 使用验证框架 Bean Validation(上)》:四、Controller 普通参数验证与视图错误信息的展示对于 form 表单提交绑定到对象的验证方式,上面已经介绍了。但是在很多时候,我们是通过普通传参来调用接口的。 比如:http://localhost:...
  • catoop
  • catoop
  • 2016-04-29 22:34
  • 21611

使用JSR-303 Validation进行验证

JSR-303是一个数据验证的规范,这里我不会讲这个规范是怎么回事,只会讲一下JSR-303在SpringMVC中的应用。JSR-303只是一个规范,而Spring也没有对这一规范进行实现,那么当我们在SpringMVC中需要使用到JSR-303的时候就需要我们提供一个对JSR-303规范的实现,H...
  • lu930124
  • lu930124
  • 2016-09-29 09:09
  • 1680

JSR-303规范,Bean Validation

一: JSR 303是JAVA EE 6中的一项子规范,叫做Bean Validation,官方参考实现是Hibernate Validator,此实现与Hibernate ORM没有任何关系。JSR 303用于对Java Bean中的字段的值进行验证。 本教程翻译自Hibernate Va...
  • GeneralYY0
  • GeneralYY0
  • 2011-11-28 14:39
  • 10196

JSR 303 自定义注解

首先先简单说一下,使用注解进行服务端验证的好处。使用注解对从前端传进来的参数进行验证,这样我们就不用在参数部分写重复的代码,有解耦也有实现代码复用的作用。这里先做一个简单地笔记,有空的时候再用自己在工作中的例子作为补充。 参考资料: 1、http://my.oschina.net/u/72622...
  • lw_power
  • lw_power
  • 2015-07-09 19:49
  • 1458

JSR303校验 —— hibernate-validator实现

本文是Spring+SpringMVC+Mybatis项目,但JSR303校验使用的是Hibernate提供的实现。 1. 加入Maven <!-- JSR303数据校验支持:tomcat7及以上的服务器 --> org.hibernate hibernat...
  • yifanSJ
  • yifanSJ
  • 2018-01-06 22:38
  • 38

Hibernate Validator实践之一 入门篇

源自:http://www.jizhenfang.cn/?p=17 Hibernate Validator实践之一 入门篇 八月 11, 20140 条评论 在后台的业务逻辑中,对数据值的校验在各层都存在(展示层,业务层,数据访问层等),并且各层校验的规则又不尽相同,如下图所示...
  • gaoshanliushui2009
  • gaoshanliushui2009
  • 2015-05-31 16:27
  • 2435

Integrity constraint violation: 1062 Duplicata du champ '0' pour la clef 'PRIMARY'解决方法

Integrity constraint violation – yii\db\IntegrityException SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicata du champ '0&...
  • panjican
  • panjican
  • 2016-03-09 17:01
  • 8678

Bean Validation 技术规范特性概述

http://www.ibm.com/developerworks/cn/java/j-lo-beanvalid/   Bean Validation JSR303 规范(Bean Validation 规范)提供了对 JavaEE 和 Java SE 中的 Java Bean 进行...
  • LJHABC1982
  • LJHABC1982
  • 2014-01-24 09:46
  • 10900
    个人资料
    • 访问:1625724次
    • 积分:18243
    • 等级:
    • 排名:第615名
    • 原创:155篇
    • 转载:913篇
    • 译文:34篇
    • 评论:222条
    文章分类
    最新评论