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

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

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

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

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

从上到下来说吧,@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.

下面就来实现这个约束。

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

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

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

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方法中编写验证规则。

  1. String fieldValue= BeanUtils.getProperty(value,field);
  2. String verifyFieldValue = BeanUtils.getProperty(value,verifyField);

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

  1. String messageTemplate = context.getDefaultConstraintMessageTemplate();
  2. context.disableDefaultConstraintViolation();
  3. context.buildConstraintViolationWithTemplate(messageTemplate)
  4. .addNode(verifyField)
  5. .addConstraintViolation();

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

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

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

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

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

  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. org.hibernate.validator.constraints.CreditCardNumber.message = invalid credit card number
  15. org.hibernate.validator.constraints.Email.message            = not a well-formed email address
  16. org.hibernate.validator.constraints.Length.message           = length must be between {min} and {max}
  17. org.hibernate.validator.constraints.NotBlank.message         = may not be empty
  18. org.hibernate.validator.constraints.NotEmpty.message         = may not be empty
  19. org.hibernate.validator.constraints.Range.message            = must be between {min} and {max}
  20. org.hibernate.validator.constraints.SafeHtml.message         = may have unsafe html content
  21. org.hibernate.validator.constraints.ScriptAssert.message     = script expression "{script}" didn't evaluate to true
  22. org.hibernate.validator.constraints.URL.message              = must be a valid URL
  23. ## custom constraints
  24. constraint.not.matches=two fields not matches
  25. constraint.confirmNewPassword.not.match.newPassword=two password not the same

单元测试如下:

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

测试全部通过的

创建自定义JSR303的验证约束(Creating custom constraints)的更多相关文章

  1. Collection View Programming Guide for iOS---(六)---Creating Custom Layouts

    Creating Custom Layouts 创建自定义布局 Before you start building custom layouts, consider whether doing so ...

  2. GHOST CMS - 创建自定义主页 Creating a custom home page

    创建自定义主页 Creating a custom home page 为你的网站创建一个自定义的主页是一个让你从人群中脱颖而出的好方法,并把你自己独特的印记存放在你的网上.本教程向您展示了如何在Gh ...

  3. ArcGIS Engine环境下创建自定义的ArcToolbox Geoprocessing工具

    在上一篇日志中介绍了自己通过几何的方法合并断开的线要素的ArcGIS插件式的应用程序.但是后来考虑到插件式的程序的配置和使用比较繁琐,也没有比较好的错误处理机制,于是我就把之前的程序封装成一个类似于A ...

  4. WCF 安全性之 自定义用户名密码验证

    案例下载 http://download.csdn.net/detail/woxpp/4113172 客户端调用代码 通过代理类 代理生成 参见 http://www.cnblogs.com/woxp ...

  5. 【IOS笔记】Creating Custom Content View Controllers

    Creating Custom Content View Controllers 自定义内容视图控制器 Custom content view controllers are the heart of ...

  6. 【翻译】在Ext JS和Sencha Touch中创建自定义布局

    原文:Creating Custom Layouts in Ext JS and Sencha Touch 布局系统是Sencha框架中最强大和最独特的一部分.布局会处理应用程序中每个组件的大小和位置 ...

  7. 【UiPath 中文教程】02 - 创建自定义 Activity

    在 UiPath Studio 中,活动 (Activity) 是流程自动化的基石,是构成自动化程序的最小模块.它们被包含在一个个 NuGet 包中. UiPath Studio 中有 3 类包: 官 ...

  8. View Controller Programming Guide for iOS---(四)---Creating Custom Content View Controllers

    Creating Custom Content View Controllers 创建自定义内容视图控制器 Custom content view controllers are the heart ...

  9. 在ASP.NET Core中创建自定义端点可视化图

    在上篇文章中,我为构建自定义端点可视化图奠定了基础,正如我在第一篇文章中展示的那样.该图显示了端点路由的不同部分:文字值,参数,动词约束和产生结果的端点: 在本文中,我将展示如何通过创建一个自定义的D ...

随机推荐

  1. JS传参中文乱码问题.NET

    前台js代码 window.location.href = "/product.aspx?id=2&title=" +encodeURIComponent(strtitle ...

  2. 《UML和模式应用》读书笔记(一)面向对象分析和设计简单示例

    在开始进行对象分析和设计之前,先通过“扔骰子”这个软件(游戏者扔两个骰子,如果总是是7,则赢,否则输),来简单分析下这个过程. 1:用例 需求分析,可能包括人们如何应用的场景或情节,这些都可以被编写成 ...

  3. 714. Best Time to Buy and Sell Stock with Transaction Fee

    问题 给定一个数组,第i个元素表示第i天股票的价格,可执行多次"买一次卖一次",每次执行完(卖出后)需要小费,求最大利润 Input: prices = [1, 3, 2, 8, ...

  4. java获取时间戳

    package com.ycy.test; import java.text.SimpleDateFormat; import java.util.Date; public class ItemsCo ...

  5. ABP官方文档翻译 1.6 OWIN集成

    OWIN集成 安装 使用 如果在应用程序里既使用ASP.NET MVC也使用ASP.NET Web API,需要在工程里安装Abp.Owin包. 安装 添加Abp.Owin包到主工程里(一般是web工 ...

  6. Http:设置 浏览器中MIME 类型

    http://www.163ns.com/zixun/post/4602.html 自定义MIME类型支持FLV的相关设置 网络空间支持FLV的相关设置其实很简单,就是自定义一个MIME类型 一般虚拟 ...

  7. 如何用纯 CSS 创作一个跳动的字母 i

    效果预览 在线演示 按下右侧的"点击预览"按钮可以在当前页面预览,点击链接可以全屏预览. https://codepen.io/comehope/pen/pZbrpJ 可交互视频 ...

  8. windows上面非常好用的辅助软件

    1.everything  快速查找本地文件 下载地址:http://www.voidtools.com/

  9. .net 数据缓存(二)之Redis部署

    现在的业务系统越来复杂,大型门户网站内容越来越多,数据库的数据量也越来愈大,所以有了“大数据”这一概念的出现.但是我们都知道当数据库的数据量和访问过于频繁都会影响系统整体性能体验,特别是并发量高的系统 ...

  10. 聊一聊HTML <!--…-->标签

    定义 注释标签用于在html源代码中插入注释.注释不会在浏览器上显示. 用法 根据定义的基本用法,代码如下 <!-- 这是一段注释,我不会显示在页面上 --> 浏览器的支持情况 所有浏览器 ...