当前位置: 首页 > 知识库问答 >
问题:

使用自定义验证器在Map上进行JSR-303验证

齐磊
2023-03-14

我正在用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/

@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());
        }
    }
}

共有1个答案

商经业
2023-03-14

ConstraintValidatorContext假定您正在构建对象图中实际可导航属性的路径。Bean验证实际上并不验证这一点,所以理论上您可以添加任何内容,但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" )
 类似资料:
  • 我有一个可以创建和编辑组的应用程序。每个团体都有一个独特的头衔。当有人试图创建新组时,我会检查是否有具有该标题的组,如果有,则验证失败。问题是,当有人试图编辑组而不更改标题时,根据我的自定义验证,有一个组具有该标题(有人正在编辑的标题),而我的验证失败,因此用户无法保存该组。所以我想问一下,是否有人能解决这个问题。非常感谢您的回答! 我的班级 我的验证器接口 我的验证器

  • 我的bean中有两个字段 当字段key=“A”,“value”应该跟在其他“key”的特定Regex后面时,它可以是任何内容。 如何根据键定义此值验证。

  • 在添加Spring特定的配置之前,没有进行任何验证,但也没有任何错误。可能根本没有尝试验证。 现在我已经添加了必要的@Validated类注释和bean定义: 我得到以下错误:

  • 我注意到,在Spring中,当声明使用注释的自定义验证器bean时,JSR-303验证被完全忽略。有趣的是,自定义验证器甚至不需要任何类填写或使用。Spring扫描其组件的事实似乎足以让Spring在对象绑定期间跳过JSR-303验证。从自定义验证器中删除,然后重新启动web应用程序,即可按预期启用JSR-303验证。使用注释自定义验证器有其用途,例如使用Spring Autowire依赖项。 考

  • 问题内容: Hibernate Validator 4.x中是否有跨域验证的实现(或第三方实现)?如果没有,实现跨域验证器的最干净方法是什么? 例如,如何使用API​​来验证两个bean属性是否相等(例如,验证密码字段与密码验证字段匹配)。 在注解中,我期望这样的东西: 问题答案: 每个字段约束都应由不同的验证者注释处理,换句话说,不建议对一个字段进行其他字段的验证注释检查。跨领域验证应在课程级别

  • 我试图为我的模型类实现一个自定义验证器,该验证器依赖于我的自定义bean(通过声明)。在本文中,我遵循了有关该主题的Spring文档。我的对象是根据本教程实现的。 但是,在运行我的测试时,对象中的autowired属性总是。为什么会这样? 以下是我的代码的相关部分: 我的自定义bean, 我的自定义约束 存储库使用Spring Data JPA的