由于輸入驗(yàn)證在軟件開(kāi)發(fā)中是必須的一件事情,特別是與用戶交互的軟件產(chǎn)品,驗(yàn)證用戶的潛在輸入錯(cuò)誤是必不可少的一件事情,然而各種開(kāi)源的驗(yàn)
證框架也很多,為了一統(tǒng)標(biāo)準(zhǔn),jsr303規(guī)范橫空出世了,它定義了一些標(biāo)準(zhǔn)的驗(yàn)證約束,標(biāo)準(zhǔn)畢竟是標(biāo)準(zhǔn),它不可能定義到所有的驗(yàn)證約束,它只是提供了一
些基本的常用的約束,不過(guò)它提供了一個(gè)可拓展的自定義驗(yàn)證約束。下面就來(lái)說(shuō)說(shuō)怎么樣自定義一個(gè)約束.
為了創(chuàng)建一個(gè)自定義約束,以下三個(gè)步驟是必須的。
Create a constraint annotation (首先定義一個(gè)約束注解)
Implement a validator(第二步是實(shí)現(xiàn)這個(gè)驗(yàn)證器)
Define a default error message(最后添加一條默認(rèn)的錯(cuò)誤消息即可)
假定有這么一個(gè)要求,要驗(yàn)證用戶的兩次輸入密碼必須是相同的,非常常見(jiàn)的一個(gè)要求。下面就基于這個(gè)要求來(lái)自定義一個(gè)約束。
- package org.leochen.samples;
-
- import javax.validation.Constraint;
- import javax.validation.Payload;
- import java.lang.annotation.*;
-
-
-
-
-
-
-
- @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();
- }
從上到下來(lái)說(shuō)吧,@Target表示注解可出現(xiàn)在哪些地方,比如可以出現(xiàn)在class上,field,method,又或者是在另外一個(gè)
annotation上,這里限制只能出現(xiàn)在類(lèi)和另外一個(gè)注解上,@Retention表示該注解的保存范圍是哪里,RUNTIME表示在源碼
(source)、編譯好的.class文件中保留信息,在執(zhí)行的時(shí)候會(huì)把這一些信息加載到JVM中去的.@Constraint比較重要,表示哪個(gè)驗(yàn)證
器提供驗(yàn)證。@interface表明這是一個(gè)注解,和class一樣都是關(guān)鍵字,message(),groups()和payload()這三個(gè)方法
是一個(gè)標(biāo)準(zhǔn)的約束所具備的,其中message()是必須的,{constraint.not.matches}表示該消息是要插值計(jì)算的,也就是說(shuō)是要
到資源文件中尋找這個(gè)key的,如果不加{}就表示是一個(gè)普通的消息,直接文本顯示,如果消息中有需要用到{或}符號(hào)的,需要進(jìn)行轉(zhuǎn)義,用\{和\}來(lái)表
示。groups()表示該約束屬于哪個(gè)驗(yàn)證組,在驗(yàn)證某個(gè)bean部分屬性是特別有用(也說(shuō)不清了,具體可以查看Hibernate
Validator的文檔細(xì)看) default必須是一個(gè)類(lèi)型為Class<?>[]的空數(shù)組,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.下面連個(gè)字段是我們添加進(jìn)去的,表示要驗(yàn)證字段的名稱,比如password和confirmPassword.
下面就來(lái)實(shí)現(xiàn)這個(gè)約束。
- package org.leochen.samples;
-
- import org.apache.commons.beanutils.BeanUtils;
-
- import javax.validation.ConstraintValidator;
- import javax.validation.ConstraintValidatorContext;
- import java.lang.reflect.InvocationTargetException;
-
-
-
-
-
-
- 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;
- }
- }
我們必須要實(shí)現(xiàn)ConstraintValidator這個(gè)接口,下面就來(lái)具體看看這個(gè)接口是怎么定義的吧:
- package javax.validation;
-
- import java.lang.annotation.Annotation;
-
- public interface ConstraintValidator<A extends Annotation, T> {
-
-
-
-
-
-
-
-
-
-
- void initialize(A constraintAnnotation);
-
-
-
-
-
-
-
-
-
-
-
-
-
- boolean isValid(T value, ConstraintValidatorContext context);
- }
A 表示邊界范圍為java.lang.annotation.Annotation即可,這個(gè)T參數(shù)必須滿足下面兩個(gè)限制條件:
- T must resolve to a non parameterized type (T 必須能被解析為非參數(shù)化的類(lèi)型,通俗講就是要能解析成具體類(lèi)型,比如Object,Dog,Cat之類(lèi)的,不能是一個(gè)占位符)
- or generic parameters of T must be unbounded wildcard types(或者也可以是一個(gè)無(wú)邊界范圍含有通配符的泛型類(lèi)型)
我們?cè)?code>initialize (A constraintAnnotation) 方法中獲取到要驗(yàn)證的兩個(gè)字段的名稱,在isValid方法中編寫(xiě)驗(yàn)證規(guī)則。
- String fieldValue= BeanUtils.getProperty(value,field);
- String verifyFieldValue = BeanUtils.getProperty(value,verifyField);
以上是我們把驗(yàn)證出錯(cuò)的消息放在哪個(gè)字段上顯示,一般我們是在確認(rèn)密碼上顯示密碼不一致的消息。
好了這樣我們的自定義約束就完成了,下面來(lái)使用并測(cè)試吧。
假如我們要驗(yàn)證這么一個(gè)formbean
- package org.leochen.samples;
-
-
-
-
-
-
- @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(名字必須叫這個(gè),不然你就費(fèi)好大一番勁,何苦呢是不是,基于約定來(lái))
- 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
單元測(cè)試如下:
- 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;
-
-
-
-
-
-
- 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());
- }
- }