首页 文章

使用Hibernate Validator进行交叉字段验证(JSR 303)

提问于
浏览
220

在Hibernate Validator 4.x中是否有(或第三方实现)交叉字段验证的实现?如果不是,那么实现交叉字段验证器的最简洁方法是什么?

例如,如何使用API来验证两个bean属性是否相等(例如验证密码字段是否与密码验证字段匹配) .

在注释中,我希望有类似的东西:

public class MyBean {
  @Size(min=6, max=50)
  private String pass;

  @Equals(property="pass")
  private String passVerify;
}

15 回答

  • 135

    每个字段约束应该由不同的验证器注释处理,或者换句话说,它应该对其他字段进行验证注释检查;跨领域验证应在 class 进行 . 此外,JSR-303 Section 2.2表达相同类型的多个验证的首选方式是通过注释列表 . 这允许每次匹配指定错误消息 .

    例如,验证常见表单:

    @FieldMatch.List({
            @FieldMatch(first = "password", second = "confirmPassword", message = "The password fields must match"),
            @FieldMatch(first = "email", second = "confirmEmail", message = "The email fields must match")
    })
    public class UserRegistrationForm  {
        @NotNull
        @Size(min=8, max=25)
        private String password;
    
        @NotNull
        @Size(min=8, max=25)
        private String confirmPassword;
    
        @NotNull
        @Email
        private String email;
    
        @NotNull
        @Email
        private String confirmEmail;
    }
    

    注释:

    package constraints;
    
    import constraints.impl.FieldMatchValidator;
    
    import javax.validation.Constraint;
    import javax.validation.Payload;
    import java.lang.annotation.Documented;
    import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
    import static java.lang.annotation.ElementType.TYPE;
    import java.lang.annotation.Retention;
    import static java.lang.annotation.RetentionPolicy.RUNTIME;
    import java.lang.annotation.Target;
    
    /**
     * Validation annotation to validate that 2 fields have the same value.
     * An array of fields and their matching confirmation fields can be supplied.
     *
     * Example, compare 1 pair of fields:
     * @FieldMatch(first = "password", second = "confirmPassword", message = "The password fields must match")
     * 
     * Example, compare more than 1 pair of fields:
     * @FieldMatch.List({
     *   @FieldMatch(first = "password", second = "confirmPassword", message = "The password fields must match"),
     *   @FieldMatch(first = "email", second = "confirmEmail", message = "The email fields must match")})
     */
    @Target({TYPE, ANNOTATION_TYPE})
    @Retention(RUNTIME)
    @Constraint(validatedBy = FieldMatchValidator.class)
    @Documented
    public @interface FieldMatch
    {
        String message() default "{constraints.fieldmatch}";
    
        Class<?>[] groups() default {};
    
        Class<? extends Payload>[] payload() default {};
    
        /**
         * @return The first field
         */
        String first();
    
        /**
         * @return The second field
         */
        String second();
    
        /**
         * Defines several <code>@FieldMatch</code> annotations on the same element
         *
         * @see FieldMatch
         */
        @Target({TYPE, ANNOTATION_TYPE})
        @Retention(RUNTIME)
        @Documented
                @interface List
        {
            FieldMatch[] value();
        }
    }
    

    验证者:

    package constraints.impl;
    
    import constraints.FieldMatch;
    import org.apache.commons.beanutils.BeanUtils;
    
    import javax.validation.ConstraintValidator;
    import javax.validation.ConstraintValidatorContext;
    
    public class FieldMatchValidator implements ConstraintValidator<FieldMatch, Object>
    {
        private String firstFieldName;
        private String secondFieldName;
    
        @Override
        public void initialize(final FieldMatch constraintAnnotation)
        {
            firstFieldName = constraintAnnotation.first();
            secondFieldName = constraintAnnotation.second();
        }
    
        @Override
        public boolean isValid(final Object value, final ConstraintValidatorContext context)
        {
            try
            {
                final Object firstObj = BeanUtils.getProperty(value, firstFieldName);
                final Object secondObj = BeanUtils.getProperty(value, secondFieldName);
    
                return firstObj == null && secondObj == null || firstObj != null && firstObj.equals(secondObj);
            }
            catch (final Exception ignore)
            {
                // ignore
            }
            return true;
        }
    }
    
  • 262

    我建议你另一个可能的解决方案也许不那么优雅,但更容易!

    public class MyBean {
      @Size(min=6, max=50)
      private String pass;
    
      private String passVerify;
    
      @AssertTrue(message="passVerify field should be equal than pass field")
      private boolean isValid() {
        return this.pass.equals(this.passVerify);
      }
    }
    

    isValid()方法由验证程序自动调用 .

  • 2

    我很惊讶这是开箱即用的 . 无论如何,这是一个可能的解决方案 .

    我已经创建了一个类级别验证器,而不是原始问题中描述的字段级别 .

    这是注释代码:

    package com.moa.podium.util.constraints;
    
    import static java.lang.annotation.ElementType.*;
    import static java.lang.annotation.RetentionPolicy.*;
    
    import java.lang.annotation.Documented;
    import java.lang.annotation.Retention;
    import java.lang.annotation.Target;
    
    import javax.validation.Constraint;
    import javax.validation.Payload;
    
    @Target({TYPE, ANNOTATION_TYPE})
    @Retention(RUNTIME)
    @Constraint(validatedBy = MatchesValidator.class)
    @Documented
    public @interface Matches {
    
      String message() default "{com.moa.podium.util.constraints.matches}";
    
      Class<?>[] groups() default {};
    
      Class<? extends Payload>[] payload() default {};
    
      String field();
    
      String verifyField();
    }
    

    验证器本身:

    package com.moa.podium.util.constraints;
    
    import org.mvel2.MVEL;
    
    import javax.validation.ConstraintValidator;
    import javax.validation.ConstraintValidatorContext;
    
    public class MatchesValidator implements ConstraintValidator<Matches, Object> {
    
      private String field;
      private String verifyField;
    
    
      public void initialize(Matches constraintAnnotation) {
        this.field = constraintAnnotation.field();
        this.verifyField = constraintAnnotation.verifyField();
      }
    
      public boolean isValid(Object value, ConstraintValidatorContext context) {
        Object fieldObj = MVEL.getProperty(field, value);
        Object verifyFieldObj = MVEL.getProperty(verifyField, value);
    
        boolean neitherSet = (fieldObj == null) && (verifyFieldObj == null);
    
        if (neitherSet) {
          return true;
        }
    
        boolean matches = (fieldObj != null) && fieldObj.equals(verifyFieldObj);
    
        if (!matches) {
          context.disableDefaultConstraintViolation();
          context.buildConstraintViolationWithTemplate("message")
              .addNode(verifyField)
              .addConstraintViolation();
        }
    
        return matches;
      }
    }
    

    请注意,我已使用MVEL检查要验证的对象的属性 . 这可以用标准反射API替换,或者如果它是您正在验证的特定类,则访问器方法本身 .

    然后可以在bean上使用@Matches注释,如下所示:

    @Matches(field="pass", verifyField="passRepeat")
    public class AccountCreateForm {
    
      @Size(min=6, max=50)
      private String pass;
      private String passRepeat;
    
      ...
    }
    

    作为免责声明,我在最后5分钟写了这篇文章,所以我可能还没有解决所有的错误 . 如果出现任何问题,我会更新答案 .

  • 3

    使用Hibernate Validator 4.1.0.Final我建议使用@ScriptAssert:

    @ScriptAssert(lang = "javascript", script = "_this.passVerify.equals(_this.pass)")
    public class MyBean {
      @Size(min=6, max=50)
      private String pass;
    
      private String passVerify;
    }
    

    自定义类级验证器@Matches解决方案没有问题 .

  • 27

    可以通过创建自定义约束来完成跨字段验证 .

    示例: - 比较User实例的password和confirmPassword字段 .

    CompareStrings

    @Target({TYPE})
    @Retention(RUNTIME)
    @Constraint(validatedBy=CompareStringsValidator.class)
    @Documented
    public @interface CompareStrings {
        String[] propertyNames();
        StringComparisonMode matchMode() default EQUAL;
        boolean allowNull() default false;
        String message() default "";
        Class<?>[] groups() default {};
        Class<? extends Payload>[] payload() default {};
    }
    

    StringComparisonMode

    public enum StringComparisonMode {
        EQUAL, EQUAL_IGNORE_CASE, NOT_EQUAL, NOT_EQUAL_IGNORE_CASE
    }
    

    CompareStringsValidator

    public class CompareStringsValidator implements ConstraintValidator<CompareStrings, Object> {
    
        private String[] propertyNames;
        private StringComparisonMode comparisonMode;
        private boolean allowNull;
    
        @Override
        public void initialize(CompareStrings constraintAnnotation) {
            this.propertyNames = constraintAnnotation.propertyNames();
            this.comparisonMode = constraintAnnotation.matchMode();
            this.allowNull = constraintAnnotation.allowNull();
        }
    
        @Override
        public boolean isValid(Object target, ConstraintValidatorContext context) {
            boolean isValid = true;
            List<String> propertyValues = new ArrayList<String> (propertyNames.length);
            for(int i=0; i<propertyNames.length; i++) {
                String propertyValue = ConstraintValidatorHelper.getPropertyValue(String.class, propertyNames[i], target);
                if(propertyValue == null) {
                    if(!allowNull) {
                        isValid = false;
                        break;
                    }
                } else {
                    propertyValues.add(propertyValue);
                }
            }
    
            if(isValid) {
                isValid = ConstraintValidatorHelper.isValid(propertyValues, comparisonMode);
            }
    
            if (!isValid) {
              /*
               * if custom message was provided, don't touch it, otherwise build the
               * default message
               */
              String message = context.getDefaultConstraintMessageTemplate();
              message = (message.isEmpty()) ?  ConstraintValidatorHelper.resolveMessage(propertyNames, comparisonMode) : message;
    
              context.disableDefaultConstraintViolation();
              ConstraintViolationBuilder violationBuilder = context.buildConstraintViolationWithTemplate(message);
              for (String propertyName : propertyNames) {
                NodeBuilderDefinedContext nbdc = violationBuilder.addNode(propertyName);
                nbdc.addConstraintViolation();
              }
            }    
    
            return isValid;
        }
    }
    

    ConstraintValidatorHelper

    public abstract class ConstraintValidatorHelper {
    
    public static <T> T getPropertyValue(Class<T> requiredType, String propertyName, Object instance) {
            if(requiredType == null) {
                throw new IllegalArgumentException("Invalid argument. requiredType must NOT be null!");
            }
            if(propertyName == null) {
                throw new IllegalArgumentException("Invalid argument. PropertyName must NOT be null!");
            }
            if(instance == null) {
                throw new IllegalArgumentException("Invalid argument. Object instance must NOT be null!");
            }
            T returnValue = null;
            try {
                PropertyDescriptor descriptor = new PropertyDescriptor(propertyName, instance.getClass());
                Method readMethod = descriptor.getReadMethod();
                if(readMethod == null) {
                    throw new IllegalStateException("Property '" + propertyName + "' of " + instance.getClass().getName() + " is NOT readable!");
                }
                if(requiredType.isAssignableFrom(readMethod.getReturnType())) {
                    try {
                        Object propertyValue = readMethod.invoke(instance);
                        returnValue = requiredType.cast(propertyValue);
                    } catch (Exception e) {
                        e.printStackTrace(); // unable to invoke readMethod
                    }
                }
            } catch (IntrospectionException e) {
                throw new IllegalArgumentException("Property '" + propertyName + "' is NOT defined in " + instance.getClass().getName() + "!", e);
            }
            return returnValue; 
        }
    
        public static boolean isValid(Collection<String> propertyValues, StringComparisonMode comparisonMode) {
            boolean ignoreCase = false;
            switch (comparisonMode) {
            case EQUAL_IGNORE_CASE:
            case NOT_EQUAL_IGNORE_CASE:
                ignoreCase = true;
            }
    
            List<String> values = new ArrayList<String> (propertyValues.size());
            for(String propertyValue : propertyValues) {
                if(ignoreCase) {
                    values.add(propertyValue.toLowerCase());
                } else {
                    values.add(propertyValue);
                }
            }
    
            switch (comparisonMode) {
            case EQUAL:
            case EQUAL_IGNORE_CASE:
                Set<String> uniqueValues = new HashSet<String> (values);
                return uniqueValues.size() == 1 ? true : false;
            case NOT_EQUAL:
            case NOT_EQUAL_IGNORE_CASE:
                Set<String> allValues = new HashSet<String> (values);
                return allValues.size() == values.size() ? true : false;
            }
    
            return true;
        }
    
        public static String resolveMessage(String[] propertyNames, StringComparisonMode comparisonMode) {
            StringBuffer buffer = concatPropertyNames(propertyNames);
            buffer.append(" must");
            switch(comparisonMode) {
            case EQUAL:
            case EQUAL_IGNORE_CASE:
                buffer.append(" be equal");
                break;
            case NOT_EQUAL:
            case NOT_EQUAL_IGNORE_CASE:
                buffer.append(" not be equal");
                break;
            }
            buffer.append('.');
            return buffer.toString();
        }
    
        private static StringBuffer concatPropertyNames(String[] propertyNames) {
            //TODO improve concating algorithm
            StringBuffer buffer = new StringBuffer();
            buffer.append('[');
            for(String propertyName : propertyNames) {
                char firstChar = Character.toUpperCase(propertyName.charAt(0));
                buffer.append(firstChar);
                buffer.append(propertyName.substring(1));
                buffer.append(", ");
            }
            buffer.delete(buffer.length()-2, buffer.length());
            buffer.append("]");
            return buffer;
        }
    }
    

    User

    @CompareStrings(propertyNames={"password", "confirmPassword"})
    public class User {
        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; }
    }
    

    Test

    public void test() {
            User user = new User();
            user.setPassword("password");
            user.setConfirmPassword("paSSword");
            Set<ConstraintViolation<User>> violations = beanValidator.validate(user);
            for(ConstraintViolation<User> violation : violations) {
                logger.debug("Message:- " + violation.getMessage());
            }
            Assert.assertEquals(violations.size(), 1);
        }
    

    Output Message:- [Password, ConfirmPassword] must be equal.

    通过使用CompareStrings验证约束,我们还可以比较两个以上的属性,并且我们可以混合四种字符串比较方法中的任何一种 .

    ColorChoice

    @CompareStrings(propertyNames={"color1", "color2", "color3"}, matchMode=StringComparisonMode.NOT_EQUAL, message="Please choose three different colors.")
    public class ColorChoice {
    
        private String color1;
        private String color2;
        private String color3;
            ......
    }
    

    Test

    ColorChoice colorChoice = new ColorChoice();
            colorChoice.setColor1("black");
            colorChoice.setColor2("white");
            colorChoice.setColor3("white");
            Set<ConstraintViolation<ColorChoice>> colorChoiceviolations = beanValidator.validate(colorChoice);
            for(ConstraintViolation<ColorChoice> violation : colorChoiceviolations) {
                logger.debug("Message:- " + violation.getMessage());
            }
    

    Output Message:- Please choose three different colors.

    同样,我们可以使用CompareNumbers,CompareDates等跨字段验证约束 .

    P.S. 我没有在 生产环境 环境下测试过这段代码(虽然我在开发环境下对它进行了测试),所以请将此代码视为Milestone Release . 如果您发现了错误,请写一个好评 . :)

  • 16

    我试过Alberthoven的例子(hibernate-validator 4.0.2.GA),我得到一个ValidationException:“带注释的方法必须遵循JavaBeans命名约定 . match()没有 . “也是 . 将方法从“匹配”重命名为“isValid”后,它可以正常工作 .

    public class Password {
    
        private String password;
    
        private String retypedPassword;
    
        public Password(String password, String retypedPassword) {
            super();
            this.password = password;
            this.retypedPassword = retypedPassword;
        }
    
        @AssertTrue(message="password should match retyped password")
        private boolean isValid(){
            if (password == null) {
                return retypedPassword == null;
            } else {
                return password.equals(retypedPassword);
            }
        }
    
        public String getPassword() {
            return password;
        }
    
        public String getRetypedPassword() {
            return retypedPassword;
        }
    
    }
    
  • 9

    如果您正在使用Spring Framework,那么您可以使用Spring Expression Language(SpEL) . 我写了一个小型库,它提供了基于SpEL的JSR-303验证器 - 它使得跨场验证变得轻而易举!看看https://github.com/jirutka/validator-spring .

    这将验证密码字段的长度和相等性 .

    @SpELAssert(value = "pass.equals(passVerify)",
                message = "{validator.passwords_not_same}")
    public class MyBean {
    
        @Size(min = 6, max = 50)
        private String pass;
    
        private String passVerify;
    }
    

    您也可以轻松修改此选项,以便仅在不为空时才验证密码字段 .

    @SpELAssert(value = "pass.equals(passVerify)",
                applyIf = "pass || passVerify",
                message = "{validator.passwords_not_same}")
    public class MyBean {
    
        @Size(min = 6, max = 50)
        private String pass;
    
        private String passVerify;
    }
    
  • 6

    使用此dedicated JSR-303 constraint library中的@EqualProperties

    @EqualProperties({"pass", "passVerify"})
    public class MyBean {
    
      @Size(min=6, max=50)
      @NotNull
      private String pass;
    
      @NotNull
      private String passVerify;
    
    }
    
  • 4

    我没有评论第一个答案的声誉,但想补充一点,我已经为获胜答案添加了单元测试,并有以下观察:

    • 如果你得到第一个或字段名称错误,那么你会得到一个验证错误,好像这些值因拼写错误而被绊倒,例如

    @FieldMatch(first =“invalidFieldName1”,second =“validFieldName2”)

    • 验证器将接受等效的数据类型,即这些将全部通过FieldMatch传递:

    private String stringField =“1”; private Integer integerField = new Integer(1)private int intField = 1;

    • 如果字段属于未实现等于的对象类型,则验证将失败 .
  • 2

    我喜欢Jakub Jirutka使用Spring Expression Language的想法 . 如果你不想添加另一个库/依赖项(假设你已经使用了Spring),这里是他的想法的简化实现 .

    约束:

    @Constraint(validatedBy=ExpressionAssertValidator.class)
    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface ExpressionAssert {
        String message() default "expression must evaluate to true";
        Class<?>[] groups() default {};
        Class<? extends Payload>[] payload() default {};
        String value();
    }
    

    验证者:

    public class ExpressionAssertValidator implements ConstraintValidator<ExpressionAssert, Object> {
        private Expression exp;
    
        public void initialize(ExpressionAssert annotation) {
            ExpressionParser parser = new SpelExpressionParser();
            exp = parser.parseExpression(annotation.value());
        }
    
        public boolean isValid(Object value, ConstraintValidatorContext context) {
            return exp.getValue(value, Boolean.class);
        }
    }
    

    像这样申请:

    @ExpressionAssert(value="pass == passVerify", message="passwords must be same")
    public class MyBean {
        @Size(min=6, max=50)
        private String pass;
        private String passVerify;
    }
    
  • -1

    非常好的解决方案bradhouse . 有没有办法将@Matches注释应用于多个字段?

    编辑:这是我提出的解决这个问题的解决方案,我修改了Constraint以接受数组而不是单个值:

    @Matches(fields={"password", "email"}, verifyFields={"confirmPassword", "confirmEmail"})
    public class UserRegistrationForm  {
    
        @NotNull
        @Size(min=8, max=25)
        private String password;
    
        @NotNull
        @Size(min=8, max=25)
        private String confirmPassword;
    
    
        @NotNull
        @Email
        private String email;
    
        @NotNull
        @Email
        private String confirmEmail;
    }
    

    注释的代码:

    package springapp.util.constraints;
    
    import static java.lang.annotation.ElementType.*;
    import static java.lang.annotation.RetentionPolicy.*;
    
    import java.lang.annotation.Documented;
    import java.lang.annotation.Retention;
    import java.lang.annotation.Target;
    
    import javax.validation.Constraint;
    import javax.validation.Payload;
    
    @Target({TYPE, ANNOTATION_TYPE})
    @Retention(RUNTIME)
    @Constraint(validatedBy = MatchesValidator.class)
    @Documented
    public @interface Matches {
    
      String message() default "{springapp.util.constraints.matches}";
    
      Class<?>[] groups() default {};
    
      Class<? extends Payload>[] payload() default {};
    
      String[] fields();
    
      String[] verifyFields();
    }
    

    并实施:

    package springapp.util.constraints;
    
    import javax.validation.ConstraintValidator;
    import javax.validation.ConstraintValidatorContext;
    
    import org.apache.commons.beanutils.BeanUtils;
    
    public class MatchesValidator implements ConstraintValidator<Matches, Object> {
    
        private String[] fields;
        private String[] verifyFields;
    
        public void initialize(Matches constraintAnnotation) {
            fields = constraintAnnotation.fields();
            verifyFields = constraintAnnotation.verifyFields();
        }
    
        public boolean isValid(Object value, ConstraintValidatorContext context) {
    
            boolean matches = true;
    
            for (int i=0; i<fields.length; i++) {
                Object fieldObj, verifyFieldObj;
                try {
                    fieldObj = BeanUtils.getProperty(value, fields[i]);
                    verifyFieldObj = BeanUtils.getProperty(value, verifyFields[i]);
                } catch (Exception e) {
                    //ignore
                    continue;
                }
                boolean neitherSet = (fieldObj == null) && (verifyFieldObj == null);
                if (neitherSet) {
                    continue;
                }
    
                boolean tempMatches = (fieldObj != null) && fieldObj.equals(verifyFieldObj);
    
                if (!tempMatches) {
                    addConstraintViolation(context, fields[i]+ " fields do not match", verifyFields[i]);
                }
    
                matches = matches?tempMatches:matches;
            }
            return matches;
        }
    
        private void addConstraintViolation(ConstraintValidatorContext context, String message, String field) {
            context.disableDefaultConstraintViolation();
            context.buildConstraintViolationWithTemplate(message).addNode(field).addConstraintViolation();
        }
    }
    
  • 1

    你需要明确地称它 . 在上面的示例中,bradhouse为您提供了编写自定义约束的所有步骤 .

    在您的调用者类中添加此代码 .

    ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
    validator = factory.getValidator();
    
    Set<ConstraintViolation<yourObjectClass>> constraintViolations = validator.validate(yourObject);
    

    在上述情况下,它将是

    Set<ConstraintViolation<AccountCreateForm>> constraintViolations = validator.validate(objAccountCreateForm);
    
  • 29

    为什么不试试Oval:http://oval.sourceforge.net/

    我看起来它支持OGNL所以也许你可以通过更自然的方式来实现它

    @Assert(expr = "_value ==_this.pass").
    
  • 1

    你们真棒 . 真的很棒的想法 . 我最喜欢 Alberthoven'sMcGin's ,所以我决定结合这两个想法 . 并开发一些通用解决方案来满足所有情况 . 这是我提出的解决方案 .

    @Documented
    @Constraint(validatedBy = NotFalseValidator.class)
    @Target({ElementType.METHOD, ElementType.FIELD,ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface NotFalse {
    
    
        String message() default "NotFalse";
        String[] messages();
        String[] properties();
        String[] verifiers();
    
        Class<?>[] groups() default {};
    
        Class<? extends Payload>[] payload() default {};
    
    }
    

    public class NotFalseValidator implements ConstraintValidator<NotFalse, Object> {
        private String[] properties;
        private String[] messages;
        private String[] verifiers;
        @Override
        public void initialize(NotFalse flag) {
            properties = flag.properties();
            messages = flag.messages();
            verifiers = flag.verifiers();
        }
    
        @Override
        public boolean isValid(Object bean, ConstraintValidatorContext cxt) {
            if(bean == null) {
                return true;
            }
    
            boolean valid = true;
            BeanWrapper beanWrapper = PropertyAccessorFactory.forBeanPropertyAccess(bean);
    
            for(int i = 0; i< properties.length; i++) {
               Boolean verified = (Boolean) beanWrapper.getPropertyValue(verifiers[i]);
               valid &= isValidProperty(verified,messages[i],properties[i],cxt);
            }
    
            return valid;
        }
    
        boolean isValidProperty(Boolean flag,String message, String property, ConstraintValidatorContext cxt) {
            if(flag == null || flag) {
                return true;
            } else {
                cxt.disableDefaultConstraintViolation();
                cxt.buildConstraintViolationWithTemplate(message)
                        .addPropertyNode(property)
                        .addConstraintViolation();
                return false;
            }
    
        }
    
    
    
    }
    

    @NotFalse(
            messages = {"End Date Before Start Date" , "Start Date Before End Date" } ,
            properties={"endDateTime" , "startDateTime"},
            verifiers = {"validDateRange" , "validDateRange"})
    public class SyncSessionDTO implements ControllableNode {
        @NotEmpty @NotPastDate
        private Date startDateTime;
    
        @NotEmpty
        private Date endDateTime;
    
    
    
        public Date getStartDateTime() {
            return startDateTime;
        }
    
        public void setStartDateTime(Date startDateTime) {
            this.startDateTime = startDateTime;
        }
    
        public Date getEndDateTime() {
            return endDateTime;
        }
    
        public void setEndDateTime(Date endDateTime) {
            this.endDateTime = endDateTime;
        }
    
    
        public Boolean getValidDateRange(){
            if(startDateTime != null && endDateTime != null) {
                return startDateTime.getTime() <= endDateTime.getTime();
            }
    
            return null;
        }
    
    }
    
  • 1

    解决方案与问题:How to access a field which is described in annotation property

    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface Match {
    
        String field();
    
        String message() default "";
    }
    

    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Constraint(validatedBy = MatchValidator.class)
    @Documented
    public @interface EnableMatchConstraint {
    
        String message() default "Fields must match!";
    
        Class<?>[] groups() default {};
    
        Class<? extends Payload>[] payload() default {};
    }
    

    public class MatchValidator implements  ConstraintValidator<EnableMatchConstraint, Object> {
    
        @Override
        public void initialize(final EnableMatchConstraint constraint) {}
    
        @Override
        public boolean isValid(final Object o, final ConstraintValidatorContext context) {
            boolean result = true;
            try {
                String mainField, secondField, message;
                Object firstObj, secondObj;
    
                final Class<?> clazz = o.getClass();
                final Field[] fields = clazz.getDeclaredFields();
    
                for (Field field : fields) {
                    if (field.isAnnotationPresent(Match.class)) {
                        mainField = field.getName();
                        secondField = field.getAnnotation(Match.class).field();
                        message = field.getAnnotation(Match.class).message();
    
                        if (message == null || "".equals(message))
                            message = "Fields " + mainField + " and " + secondField + " must match!";
    
                        firstObj = BeanUtils.getProperty(o, mainField);
                        secondObj = BeanUtils.getProperty(o, secondField);
    
                        result = firstObj == null && secondObj == null || firstObj != null && firstObj.equals(secondObj);
                        if (!result) {
                            context.disableDefaultConstraintViolation();
                            context.buildConstraintViolationWithTemplate(message).addPropertyNode(mainField).addConstraintViolation();
                            break;
                        }
                    }
                }
            } catch (final Exception e) {
                // ignore
                //e.printStackTrace();
            }
            return result;
        }
    }
    

    以及如何使用它?像这样:

    @Entity
    @EnableMatchConstraint
    public class User {
    
        @NotBlank
        private String password;
    
        @Match(field = "password")
        private String passwordConfirmation;
    }
    

相关问题