【问题标题】:JSR-303 Validation on Map with custom validatorJSR-303 使用自定义验证器在地图上进行验证
【发布时间】:2015-03-28 10:27:58
【问题描述】:

我正在使用 Spring 和 Thymeleaf 填写表格:

<form method="post" th:action="@{/postForm}" th:object="${myForm}"><!--/* model.addAttribute("myForm", new MyForm()) */-->
    <input type="text" th:each="id : ${idList}" th:field="*{map['__${id}__']}" /><!--/* results in map['P12345'] */-->
</form>

MyForm 看起来像这样:

public class MyForm {
    @Quantity
    private Map<String, String> map = new HashMap<String, String>();

    public Map<String, String> getMap() {
        return map;
    }

    public void setMap(Map<String, String> map) {
        this.map = map;
    }
}

如您所见,我做了一个自定义注释@Quantity,它应该检查输入值是否可以解析为BigDecimal:

@Target({METHOD, FIELD, ANNOTATION_TYPE})
@Retention(RUNTIME)
@Constraint(validatedBy = QuantityValidator.class)
@Documented
public @interface Quantity {
    String message() default "{com.example.form.validation.constraints.Quantity}";
    Class<? extends Payload>[] payload() default {};
    Class<?>[] groups() default {};
}

public class QuantityValidator implements ConstraintValidator<Quantity, Map<String, String>> {
    private final DecimalFormat format = (DecimalFormat) DecimalFormat.getInstance();
    private final ParsePosition pos = new ParsePosition(0);

    @Override
    public void initialize(Quantity quantity) {
        format.setParseBigDecimal(true);
    }

    @Override
    public boolean isValid(Map<String, String> map, ConstraintValidatorContext context) {
        List<String> invalidFieldsList = new ArrayList<String>();

        for (Map.Entry<String, String> entry : map.entrySet()) {
            String quantity = entry.getValue();
            if (quantity != null && !quantity.isEmpty()) {
                if ((BigDecimal) format.parse(quantity, pos) == null) {
                    invalidFieldsList.add(entry.getKey());
                }
            }
        }

        if (!invalidFieldsList.isEmpty()) {
            context.disableDefaultConstraintViolation();

            for (String field : invalidFieldsList) {
                context.buildConstraintViolationWithTemplate("Invalid Quantity for Field: " + field).addNode(field).addConstraintViolation();
            }

            return false;
        }

        return true;
    }
}

现在在我的 Controller 类中我正在这样做:

@Controller
public class MyController {
    @RequestMapping(value = "/postForm", method = RequestMethod.POST)
    public void postForm(@ModelAttribute @Valid MyForm myForm, BindingResult bindingResult) {
        if (!bindingResult.hasErrors()) {
            // do some stuff
        }
    }
}

但在尝试将d 放入文本字​​段以测试验证时得到NotReadablePropertyException:

java.lang.IllegalStateException: JSR-303 validated property 'map.P12345' does not have a corresponding accessor for Spring data binding - check your DataBinder's configuration (bean property versus direct field access)
Caused by:
org.springframework.beans.NotReadablePropertyException: Invalid property 'map.P12345' of bean class [com.example.form.MyForm]: Bean property 'map.P12345' is not readable or has an invalid getter method: Does the return type of the getter match the parameter type of the setter?
    at org.springframework.beans.BeanWrapperImpl.getPropertyValue(BeanWrapperImpl.java:726) ~[spring-beans-4.1.1.RELEASE.jar:4.1.1.RELEASE]
    at org.springframework.beans.BeanWrapperImpl.getPropertyValue(BeanWrapperImpl.java:717) ~[spring-beans-4.1.1.RELEASE.jar:4.1.1.RELEASE]
    at org.springframework.validation.AbstractPropertyBindingResult.getActualFieldValue(AbstractPropertyBindingResult.java:99) ~[spring-context-4.1.1.RELEASE.jar:4.1.1.RELEASE]
    at org.springframework.validation.AbstractBindingResult.getRawFieldValue(AbstractBindingResult.java:283) ~[spring-context-4.1.1.RELEASE.jar:4.1.1.RELEASE]
    at org.springframework.validation.beanvalidation.SpringValidatorAdapter.processConstraintViolations(SpringValidatorAdapter.java:143) ~[spring-context-4.1.1.RELEASE.jar:4.1.1.RELEASE]
    ... 84 more

这是我阅读并希望使用自定义验证器进行扩展的示例:http://viralpatel.net/blogs/spring-mvc-hashmap-form-example/

编辑: 在注释掉 @Valid 注释并检查 myForm.getMap() 包含的内容时,地图已正确填充:

@Controller
public class MyController {
    private final Logger log = LogManager.getLogger(getClass());

    @RequestMapping(value = "/postForm", method = RequestMethod.POST)
    public void postForm(@ModelAttribute /*@Valid*/ MyForm myForm, BindingResult bindingResult) {
        // Output:
        // P12345: d
        // P67890: 
        for (Map.Entry<String, String> entry : myForm.getMap().entrySet()) {
            log.debug(entry.getKey() + ": " + entry.getValue());
        }
    }
}

【问题讨论】:

    标签: java spring bean-validation thymeleaf


    【解决方案1】:

    ConstraintValidatorContext 假定您正在构建指向对象图中实际可导航属性的路径。 Bean Validation 实际上并没有对此进行验证,因此理论上您可以添加任何内容,但似乎 Spring 集成确实使用了该路径。可能是将错误映射到正确的 UI 元素(我不知道 Spring 代码)。您需要做的是确保为正确的节点添加约束违规。 API 实际上允许遍历地图。看起来像:

    context.buildConstraintViolationWithTemplate( message )
            .addPropertyNode( "foo" )
            .addPropertyNode( null ).inIterable().atKey( "test" )
            .addConstraintViolation();  
    

    'null' 在这种情况下表示映射到键的值。这与向值本身的属性添加违规相反,如下所示:

    context.buildConstraintViolationWithTemplate( message )
            .addPropertyNode( "foo" )
            .addPropertyNode( "bar" ).inIterable().atKey( "test" )
    

    【讨论】:

    • 非常感谢!我使用了旧的验证 API 版本(1.0.0.GA,它是 hibernate-validator 4.2.0 的依赖项)。更新它并使用addPropertyNode(null).inIterable().atKey(field); 就像一个魅力。
    • 太棒了。我正要说,你应该验证你的 Validator/Bean Validation 版本。这个特殊的 API 可以在 Bean Validation 1.0 和 1.1 之间改变。我给你的例子是基于 Bean Validation 1.1
    • 我不明白 null 和 "bar" 的区别在这里——你介意用 OP 的例子来解释一下吗?
    猜你喜欢
    • 2011-08-09
    • 2012-05-12
    • 2015-07-31
    • 1970-01-01
    • 2018-01-30
    • 1970-01-01
    • 1970-01-01
    • 2018-08-24
    • 2015-01-10
    相关资源
    最近更新 更多