创建自定义JSR303的验证约束(Creating custom constraints)
转载:http://clongjava.iteye.com/blog/1317649
由于输入验证在软件开发中是必须的一件事情,特别是与用户交互的软件产品,验证用户的潜在输入错误是必不可少的一件事情,然而各种开源的验证框架也很多,为了一统标准,jsr303规范横空出世了,它定义了一些标准的验证约束,标准毕竟是标准,它不可能定义到所有的验证约束,它只是提供了一些基本的常用的约束,不过它提供了一个可拓展的自定义验证约束。下面就来说说怎么样自定义一个约束.
为了创建一个自定义约束,以下三个步骤是必须的。
• Create a constraint annotation (首先定义一个约束注解)
• Implement a validator(第二步是实现这个验证器)
• Define a default error message(最后添加一条默认的错误消息即可)
假定有这么一个要求,要验证用户的两次输入密码必须是相同的,非常常见的一个要求。下面就基于这个要求来自定义一个约束。
- package org.leochen.samples;
- import javax.validation.Constraint;
- import javax.validation.Payload;
- import java.lang.annotation.*;
- /**
- * User: leochen
- * Date: 11-12-8
- * Time: 下午11:31
- */
- @Target({ElementType.TYPE,ElementType.ANNOTATION_TYPE})
- @Retention(RetentionPolicy.RUNTIME)
- @Constraint(validatedBy = MatchesValidator.class)
- @Documented
- public @interface Matches {
- String message() default "{constraint.not.matches}";
- Class<?>[] groups() default {};
- Class<? extends Payload>[] payload() default {};
- String field();
- String verifyField();
- }
从上到下来说吧,@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.
下面就来实现这个约束。
- package org.leochen.samples;
- import org.apache.commons.beanutils.BeanUtils;
- import javax.validation.ConstraintValidator;
- import javax.validation.ConstraintValidatorContext;
- import java.lang.reflect.InvocationTargetException;
- /**
- * User: leochen
- * Date: 11-12-8
- * Time: 下午11:39
- */
- public class MatchesValidator implements ConstraintValidator<Matches,Object>{
- private String field;
- private String verifyField;
- public void initialize(Matches matches) {
- this.field = matches.field();
- this.verifyField = matches.verifyField();
- }
- public boolean isValid(Object value, ConstraintValidatorContext context) {
- try {
- String fieldValue= BeanUtils.getProperty(value,field);
- String verifyFieldValue = BeanUtils.getProperty(value,verifyField);
- boolean valid = (fieldValue == null) && (verifyFieldValue == null);
- if(valid){
- return true;
- }
- boolean match = (fieldValue!=null) && fieldValue.equals(verifyFieldValue);
- if(!match){
- String messageTemplate = context.getDefaultConstraintMessageTemplate();
- context.disableDefaultConstraintViolation();
- context.buildConstraintViolationWithTemplate(messageTemplate)
- .addNode(verifyField)
- .addConstraintViolation();
- }
- return match;
- } catch (IllegalAccessException e) {
- e.printStackTrace();
- } catch (InvocationTargetException e) {
- e.printStackTrace();
- } catch (NoSuchMethodException e) {
- e.printStackTrace();
- }
- return true;
- }
- }
我们必须要实现ConstraintValidator这个接口,下面就来具体看看这个接口是怎么定义的吧:
- package javax.validation;
- import java.lang.annotation.Annotation;
- public interface ConstraintValidator<A extends Annotation, T> {
- /**
- * Initialize the validator in preparation for isValid calls.
- * The constraint annotation for a given constraint declaration
- * is passed.
- * <p/>
- * This method is guaranteed to be called before any use of this instance for
- * validation.
- *
- * @param constraintAnnotation annotation instance for a given constraint declaration
- */
- void initialize(A constraintAnnotation);
- /**
- * Implement the validation logic.
- * The state of <code>value</code> must not be altered.
- *
- * This method can be accessed concurrently, thread-safety must be ensured
- * by the implementation.
- *
- * @param value object to validate
- * @param context context in which the constraint is evaluated
- *
- * @return false if <code>value</code> does not pass the constraint
- */
- boolean isValid(T value, ConstraintValidatorContext context);
- }
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
方法中获取到要验证的两个字段的名称,在isValid方法中编写验证规则。
(A
constraintAnnotation)
- String fieldValue= BeanUtils.getProperty(value,field);
- String verifyFieldValue = BeanUtils.getProperty(value,verifyField);
通过反射获取验证字段的值,由于我们要实现的是一个密码和确认密码一致的问题,而这两个字段类型都是java.lang.String类型,所以我们直接通过BeanUtils来获取他们各自的值。
- String messageTemplate = context.getDefaultConstraintMessageTemplate();
- context.disableDefaultConstraintViolation();
- context.buildConstraintViolationWithTemplate(messageTemplate)
- .addNode(verifyField)
- .addConstraintViolation();
以上是我们把验证出错的消息放在哪个字段上显示,一般我们是在确认密码上显示密码不一致的消息。
好了这样我们的自定义约束就完成了,下面来使用并测试吧。
假如我们要验证这么一个formbean
- package org.leochen.samples;
- /**
- * User: leochen
- * Date: 11-12-20
- * Time: 下午4:04
- */
- @Matches(field = "password", verifyField = "confirmPassword",
- message = "{constraint.confirmNewPassword.not.match.newPassword}")
- public class TwoPasswords {
- private String password;
- private String confirmPassword;
- public String getPassword() {
- return password;
- }
- public void setPassword(String password) {
- this.password = password;
- }
- public String getConfirmPassword() {
- return confirmPassword;
- }
- public void setConfirmPassword(String confirmPassword) {
- this.confirmPassword = confirmPassword;
- }
- }
在路径下放入我们的资源文件:ValidationMessages.properties(名字必须叫这个,不然你就费好大一番劲,何苦呢是不是,基于约定来)
- javax.validation.constraints.AssertFalse.message = must be false
- javax.validation.constraints.AssertTrue.message = must be true
- javax.validation.constraints.DecimalMax.message = must be less than or equal to {value}
- javax.validation.constraints.DecimalMin.message = must be greater than or equal to {value}
- javax.validation.constraints.Digits.message = numeric value out of bounds (<{integer} digits>.<{fraction} digits> expected)
- javax.validation.constraints.Future.message = must be in the future
- javax.validation.constraints.Max.message = must be less than or equal to {value}
- javax.validation.constraints.Min.message = must be greater than or equal to {value}
- javax.validation.constraints.NotNull.message = may not be null
- javax.validation.constraints.Null.message = must be null
- javax.validation.constraints.Past.message = must be in the past
- javax.validation.constraints.Pattern.message = must match "{regexp}"
- javax.validation.constraints.Size.message = size must be between {min} and {max}
- org.hibernate.validator.constraints.CreditCardNumber.message = invalid credit card number
- org.hibernate.validator.constraints.Email.message = not a well-formed email address
- org.hibernate.validator.constraints.Length.message = length must be between {min} and {max}
- org.hibernate.validator.constraints.NotBlank.message = may not be empty
- org.hibernate.validator.constraints.NotEmpty.message = may not be empty
- org.hibernate.validator.constraints.Range.message = must be between {min} and {max}
- org.hibernate.validator.constraints.SafeHtml.message = may have unsafe html content
- org.hibernate.validator.constraints.ScriptAssert.message = script expression "{script}" didn't evaluate to true
- org.hibernate.validator.constraints.URL.message = must be a valid URL
- ## custom constraints
- constraint.not.matches=two fields not matches
- constraint.confirmNewPassword.not.match.newPassword=two password not the same
单元测试如下:
- package org.leochen.samples;
- import org.junit.BeforeClass;
- import org.junit.Test;
- import javax.validation.ConstraintViolation;
- import javax.validation.Validation;
- import javax.validation.Validator;
- import javax.validation.ValidatorFactory;
- import java.util.Set;
- import static junit.framework.Assert.assertEquals;
- import static junit.framework.Assert.assertNotNull;
- /**
- * User: leochen
- * Date: 11-12-20
- * Time: 下午4:06
- */
- public class TwoPasswordsTest {
- private static Validator validator;
- @BeforeClass
- public static void setUp() {
- ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
- validator = factory.getValidator();
- }
- @Test
- public void testBuildDefaultValidatorFactory() {
- ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
- Validator validator = factory.getValidator();
- assertNotNull(validator);
- }
- @Test
- public void testPasswordEqualsConfirmPassword() {
- TwoPasswords bean = new TwoPasswords();
- bean.setPassword("110");
- bean.setConfirmPassword("110");
- Set<ConstraintViolation<TwoPasswords>> constraintViolations = validator.validate(bean);
- for (ConstraintViolation<TwoPasswords> constraintViolation : constraintViolations) {
- System.out.println(constraintViolation.getMessage());
- }
- assertEquals("newPassword and confirmNewPassword should be the same.", 0, constraintViolations.size());
- }
- @Test
- public void testPasswordNotEqualsConfirmPassword() {
- TwoPasswords bean = new TwoPasswords();
- bean.setPassword("110");
- bean.setConfirmPassword("111");
- Set<ConstraintViolation<TwoPasswords>> constraintViolations = validator.validate(bean);
- assertEquals(1, constraintViolations.size());
- assertEquals("two password not the same", constraintViolations.iterator().next().getMessage());
- }
- @Test
- public void testIfTwoPasswordWereNullShouldPast() {
- TwoPasswords bean = new TwoPasswords();
- bean.setPassword(null);
- bean.setConfirmPassword(null);
- Set<ConstraintViolation<TwoPasswords>> constraintViolations = validator.validate(bean);
- assertEquals(0, constraintViolations.size());
- }
- @Test
- public void testIfOneIsNullAndOtherIsNotShouldNotPast() {
- TwoPasswords bean = new TwoPasswords();
- bean.setPassword(null);
- bean.setConfirmPassword("110");
- Set<ConstraintViolation<TwoPasswords>> constraintViolations = validator.validate(bean);
- assertEquals(1, constraintViolations.size());
- assertEquals("two password not the same", constraintViolations.iterator().next().getMessage());
- }
- }
测试全部通过的
创建自定义JSR303的验证约束(Creating custom constraints)的更多相关文章
- Collection View Programming Guide for iOS---(六)---Creating Custom Layouts
Creating Custom Layouts 创建自定义布局 Before you start building custom layouts, consider whether doing so ...
- GHOST CMS - 创建自定义主页 Creating a custom home page
创建自定义主页 Creating a custom home page 为你的网站创建一个自定义的主页是一个让你从人群中脱颖而出的好方法,并把你自己独特的印记存放在你的网上.本教程向您展示了如何在Gh ...
- ArcGIS Engine环境下创建自定义的ArcToolbox Geoprocessing工具
在上一篇日志中介绍了自己通过几何的方法合并断开的线要素的ArcGIS插件式的应用程序.但是后来考虑到插件式的程序的配置和使用比较繁琐,也没有比较好的错误处理机制,于是我就把之前的程序封装成一个类似于A ...
- WCF 安全性之 自定义用户名密码验证
案例下载 http://download.csdn.net/detail/woxpp/4113172 客户端调用代码 通过代理类 代理生成 参见 http://www.cnblogs.com/woxp ...
- 【IOS笔记】Creating Custom Content View Controllers
Creating Custom Content View Controllers 自定义内容视图控制器 Custom content view controllers are the heart of ...
- 【翻译】在Ext JS和Sencha Touch中创建自定义布局
原文:Creating Custom Layouts in Ext JS and Sencha Touch 布局系统是Sencha框架中最强大和最独特的一部分.布局会处理应用程序中每个组件的大小和位置 ...
- 【UiPath 中文教程】02 - 创建自定义 Activity
在 UiPath Studio 中,活动 (Activity) 是流程自动化的基石,是构成自动化程序的最小模块.它们被包含在一个个 NuGet 包中. UiPath Studio 中有 3 类包: 官 ...
- View Controller Programming Guide for iOS---(四)---Creating Custom Content View Controllers
Creating Custom Content View Controllers 创建自定义内容视图控制器 Custom content view controllers are the heart ...
- 在ASP.NET Core中创建自定义端点可视化图
在上篇文章中,我为构建自定义端点可视化图奠定了基础,正如我在第一篇文章中展示的那样.该图显示了端点路由的不同部分:文字值,参数,动词约束和产生结果的端点: 在本文中,我将展示如何通过创建一个自定义的D ...
随机推荐
- Element 中表单非必填数据项 必须为数字的验证问题
Element-ui 的el-form组建中,自带基本的验证功能,比如某些项必填的验证,直接加入rules 规则中即可,如下实例: 在页面中书写如下: <el-form-item label=& ...
- 为什么mysql innodb索引是B+树数据结构
1.文件很大,不可能全部存储在内存中,所以要存在磁盘上 2.索引的组织结构要尽量减少查找过程中磁盘I/O的存取次数(为什么用B-/+Tree,还跟磁盘存取原理有关) 3.B+树所有的data域在叶子节 ...
- PKU 2155 Matrix(裸二维树状数组)
题目大意:原题链接 题意很简单,就不赘诉了. 解题思路: 使用二维树状数组,很裸的题. 二维的写起来也很方便,两重循环. Add(int x,int y,int val)表示(x,y)-(n,n)矩形 ...
- log4j的配置和使用
日志记录 在应用程序中添加日志记录总的来说基于三个目的: 监视代码中变量的变化情况,周期性的记录到文件中供其他应用进行统计分析工作: 跟踪代码运行时轨迹,作为日后审计的依据:担当集成开发环境中的调试器 ...
- quartz (二) Spring+Quartz实现定时任务的配置方法
JobDetail 设置执行的任务 :CronTrigger 触发器:设置执行的时间规则 ; Scheduler // 调度器,将任务与执行时间关联 本文转自:http://w ...
- bind函数的作用
面向连接的网络应用程序分为客户端和服务器端.服务器端的执行流程一般为4步,客户端程序相对简单,一般需要两个步骤. 服务器端执行流程4步如下: (1)调用socket函数,建立一个套接字,该套接字用于接 ...
- oracle安装完成后目录中不论有没有tnsnames.ora和listener.ora文件 PLSQL都能连上的问题解决方法
今天遇到这个问题了,发现listener.ora文件和tnsnames.ora文件在Net Work文件夹下没有,正常情况下安装完oracle或者是oracle Client是会有的,但是在Net M ...
- STM32-串行SPI nor
源:FLASH 存储学习-串行SPI nor 1.1 SST25VF080B简介1.1.1 主要特性 关键点:容量.速度(时钟速度.读写速度).功耗. l 容量:8MBit: l 最高SPI时钟频率: ...
- 在Pycharm中配置Github
Pycharm是当前进行python开发,尤其是Django开发最好的IDE.GitHub是程序员的圣地,几乎人人都在用. 本文假设你对pycharm和github都有一定的了解,并且希望在pycha ...
- iframe自适应高度(兼容多种浏览器)
http://jingyan.baidu.com/article/b87fe19eaeb2cf5218356896.html 让iframe自适应高度,下面是实现的源码: <div id=&qu ...