`

创建自定义JSR303的验证约束(Creating custom constraints)

 
阅读更多

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

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

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

 

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

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

假如我们要验证这么一个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());
    }
}

 

分享到:
评论

相关推荐

    JSR303Test.zip

    在描述中提到的"基于JSR303的参数验证"是指利用JSR 303提供的注解来对方法参数或bean属性进行验证。这些注解,如`@NotNull`,可以很方便地添加到字段或方法参数上,以声明某些字段必须非空。`@NotNull`是其中最基础...

    SpringMVC杂记(五) JSR303数据验证

    JSR303还允许自定义验证注解和对应的验证器,通过实现`ConstraintValidator`接口,你可以创建自己的验证逻辑。例如,如果你需要验证邮箱地址的独特性,你可以定义一个`@UniqueEmail`注解,并编写一个对应的验证器。 ...

    JSR303jar包 使用的是Hibernate

    5. 自定义验证:如果预定义的约束不足以满足需求,可以创建自定义的验证注解和对应的验证器实现。 总的来说,JSR303和Hibernate Validator为Java开发带来了标准化的数据验证解决方案,它们与SpringMVC的整合使得Web...

    JSR303jar包

    JSR303 jar包是一个包含了JSR303规范实现的软件包,用于在Java项目中实现数据验证功能。这个jar包中的实现通常会包括各种预定义的校验注解,如`@NotNull`、`@Size`、`@Min`、`@Max`等,以及对应的验证逻辑。这些注解...

    JSR303 jar包 文档

    3. **验证API**:`validation-api-1.1.0.jar`包含了JSR 303的接口和枚举,如`javax.validation.constraints`包下的各种注解,以及`javax.validation`包下的验证接口和上下文对象。 4. **元数据**:JSR 303支持从元...

    佟刚_JSR303验证.

    JSR-303 是 JAVA EE 6 中的一项子规范,叫做 Bean Validation,官方参考实现是 Hibernate Validator。 此实现与 Hibernate ORM 没有任何关系。JSR 303 用于对 Java Bean 中的字段的值 进行验证。

    jsr 303需要的jar包

    JSR 303,全称为Java Specification Request 303,是Java平台的一个标准,主要关注于数据验证。这个标准定义了一套API,用于在Java应用程序中进行bean对象的数据验证,大大简化了验证逻辑的编写。JSR 303的实现之一...

    JSR303.zip

    JSR303提供了一些预定义的验证器,同时支持自定义验证器以满足特定需求。 3. **验证上下文**:在验证过程中,验证上下文(ValidationContext)用于存储验证相关信息,如待验证的对象、错误信息等。 4. **验证接口*...

    springmvc_jsr303

    项目中所需的jar包在压缩包中已提供,项目中用到了JSR303和hibernate-validator的技术,大量采用了注解@NotNull,@Email,@Length,@Max,@Pattern,@Size等,此外还采用了自定义注解,验证信息全部配置在属性文件中.

    hibernate validator jsr303

    同时,它还支持自定义验证注解,以满足特定业务场景的验证需求。此外,Hibernate Validator 还提供了一个强大的表达式语言——EL (Expression Language),允许开发者在注解中使用复杂的条件逻辑。 **主要组件** 1....

    JSR303依赖的jar包

    validator-5.2.4.Final.jar、hibernate-validator-annotation-processor-5.2.4.Final.jar、hibernate-validator-cdi-5.2.4.Final.jar、validation-api-1.1.0.Final.jar四个jar包,用于JSR303的校验。

    23 Spring Core参数校验之JSR303_JSR-349注解-慕课专栏1

    Spring中的`Validator`接口是实现参数校验的核心,它允许自定义验证逻辑并可在任何层面上应用,如Web层、DAO层等。要使用`Validator`,需要实现该接口并编写相应的验证方法。然而,使用JSR 303/349/380注解的方式...

    JSR303需要用到的3个jar包

    此外,Hibernate Validator还支持自定义验证注解和约束,以及国际化消息支持,以适应不同的应用场景。 2. **JBoss Logging**:`jboss-logging-3.1.0.CR2.jar`是JBoss社区开发的日志框架,它为应用提供了一个统一的...

    springmvc&JSR303;的jar包

    2. 自定义验证注解:开发者可以创建自定义的验证注解,并通过实现Validator接口来定义验证逻辑。 3. 验证器:使用Hibernate Validator(JSR303/349的参考实现)或其他兼容实现,可以在运行时自动执行验证。 4. 进行...

    jsr303校验.txt

    除了以上内置的注解外,JSR 303 还支持用户自定义验证逻辑。这通常涉及到创建一个新的注解和一个对应的验证器来实现复杂的验证逻辑。 #### 示例:自定义注解 假设我们需要验证一个字段是否为有效的手机号码: ```...

    服务端JSR303参数校验md,学习代码

    3. `服务端JSR303参数校验.md`:这个文件是核心学习材料,可能详细介绍了JSR303的规范、如何添加验证注解、自定义验证逻辑,以及如何在实际项目中集成和使用JSR303。 4. `javaweb\javaweb.md`:可能是一份更广泛的...

    jsr303.jar

    JSR303还支持自定义验证注解和约束,允许开发者根据业务需求创建自己的验证逻辑。这通常通过实现ConstraintValidator接口来完成,该接口有两个泛型参数,一个是自定义注解的类型,另一个是被验证的对象类型。 在...

    JSR303+AOP数据校验

    JSR303(Java Bean Validation)是Java平台上的一个规范,它定义了一种标准的方式来验证对象属性,以满足业务规则。AOP(Aspect Oriented Programming,面向切面编程)则是一种编程范式,它允许程序员定义“切面”,...

Global site tag (gtag.js) - Google Analytics