简体   繁体   English

将 JSR-303 验证错误转换为 Spring 的 BindingResult

[英]Convert JSR-303 validation errors to Spring's BindingResult

I have the following code in a Spring controller:我在 Spring 控制器中有以下代码:

@Autowired
private javax.validation.Validator validator;

@RequestMapping(value = "/submit", method = RequestMethod.POST)
public String submitForm(CustomForm form) {
    Set<ConstraintViolation<CustomForm>> errors = validator.validate(form);
    ...
}

Is it possible to map errors to Spring's BindingResult object without manually going through all the errors and adding them to the BindingResult ?是否可以将errors映射到 Spring 的BindingResult对象,而无需手动检查所有错误并将它们添加到BindingResult Something like this:像这样的东西:

// NOTE: this is imaginary code
BindingResult bindingResult = BindingResult.fromConstraintViolations(errors);

I know it is possible to annotate the CustomForm parameter with @Valid and let Spring inject BindingResult as another method's parameter, but it's not an option in my case.我知道可以使用@Valid注释CustomForm参数并让 Spring 注入BindingResult作为另一个方法的参数,但在我的情况下这不是一个选项。

// I know this is possible, but doesn't work for me
public String submitForm(@Valid CustomForm form, BindingResult bindingResult) {
    ...
}

A simpler approach could be to use Spring's abstraction org.springframework.validation.Validator instead, you can get hold of a validator by having this bean in the context:一种更简单的方法可能是使用 Spring 的抽象org.springframework.validation.Validator ,您可以通过在上下文中使用此 bean 来获取验证器:

<bean id="jsr303Validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean" />

@Autowired @Qualifier("jsr303Validator") Validator validator;

With this abstraction in place, you can use the validator this way, passing in your bindingResult:有了这个抽象,你就可以这样使用验证器,传入你的 bindingResult :

validator.validate(obj, bindingResult);

Spring uses a SpringValidatorAdapter to convert javax.validation.ConstraintViolation objects to ObjectError or FieldError objects, as found in the binding result. Spring 使用 SpringValidatorAdapter 将 javax.validation.ConstraintViolation 对象转换为 ObjectError 或 FieldError 对象,如绑定结果中所示。 The BindStatus then uses a message source (like the web application context itself) to translate the errors.然后 BindStatus 使用消息源(如 Web 应用程序上下文本身)来转换错误。 In short, you could do:简而言之,你可以这样做:

SpringValidatorAdapter springValidator = new SpringValidatorAdapter(validator);
BindingResult bindingResult= new BeanPropertyBindingResult(myBeanToValidate, "myBeanName");
springValidator.validate(myBeanToValidate, bindingResult);

This is easier when writing a unit test, because you don't even need to create a Spring context.这在编写单元测试时更容易,因为您甚至不需要创建 Spring 上下文。

@RequestMapping(value = "/submit", method = RequestMethod.POST)
public String submitForm(CustomForm form) {
    Set<ConstraintViolation<CustomForm>> errors = validator.validate(form);

    BindingResult bindingResult = toBindingResult(errors, form, "form");
    ...
}

private BindingResult toBindingResult(ConstraintViolationException e, Object object, String objectName) {
    BindingResult bindingResult = new BeanPropertyBindingResult(object, objectName);
    new AddConstraintViolationsToErrors().addConstraintViolations(e.getConstraintViolations(), bindingResult);
    return bindingResult;
}

private static class AddConstraintViolationsToErrors extends SpringValidatorAdapter {
    public AddConstraintViolationsToErrors() {
        super(Validation.buildDefaultValidatorFactory().getValidator()); // Validator is not actually used
    }

    @SuppressWarnings({"rawtypes", "unchecked"})
    public void addConstraintViolations(Set<? super ConstraintViolation<?>> violations, Errors errors) {
        // Using raw type since processConstraintViolations specifically expects ConstraintViolation<Object>
        super.processConstraintViolations((Set) violations, errors);
    }
}

Unlike the other answers to this question, this solution handles the case where there already exists a Set<ConstraintViolation<?>> which needs to be converted to to a BindingResult .与此问题的其他答案不同,此解决方案处理已经存在需要转换为BindingResultSet<ConstraintViolation<?>>BindingResult

Explanation解释

Spring provides the SpringValidatorAdapter class to perform bean validations, storing the results in an Errors instance (note that BindingResult extends Errors ). Spring 提供SpringValidatorAdapter类来执行 bean 验证,将结果存储在Errors实例中(注意BindingResult扩展Errors )。 The normal manual use of this class would be to use it to perform the validations via the validate method:此类的正常手动使用是使用它通过validate方法执行验证:

Validator beanValidator = Validation.buildDefaultValidatorFactory().getValidator();
SpringValidatorAdapter validatorAdapter = new SpringValidatorAdapter(beanValidator);

BindException bindException = new BindException(form, "form");
validatorAdapter.validate(form, bindException);

However, this doesn't help in the case where there already exists a Set<ConstraintViolation<?>> which needs to be converted to a BindingResult .但是,这在已经存在需要转换为BindingResultSet<ConstraintViolation<?>>的情况下BindingResult

It is still possible to achieve this goal, though it does require jumping through a couple extra hoops.仍然有可能实现这个目标,尽管它确实需要跳过几个额外的箍。 SpringValidatorAdapter contains a processConstraintViolations method which converts the ConstraintViolation objects into the appropriate Spring ObjectError subtypes, and stores them on an Errors object. SpringValidatorAdapter包含一个processConstraintViolations方法,该方法将ConstraintViolation对象转换为适当的 Spring ObjectError子类型,并将它们存储在Errors对象上。 However, this method is protected, limiting its accesibility to subclasses.然而,这个方法是受保护的,限制了它对子类的访问。

This limitation can be worked around by creating a custom subclass of SpringValidatorAdapter which delegates to or exposes the protected method.可以通过创建SpringValidatorAdapter的自定义子类来解决此限制,该子类委托或公开受保护的方法。 It is not a typical usage, but it works.这不是典型的用法,但它有效。

public class AddConstraintViolationsToErrors extends SpringValidatorAdapter {
    public AddConstraintViolationsToErrors() {
        super(Validation.buildDefaultValidatorFactory().getValidator()); // Validator is not actually used
    }

    @SuppressWarnings({"rawtypes", "unchecked"})
    public void addConstraintViolations(Set<? super ConstraintViolation<?>> violations, Errors errors) {
        // Using raw type since processConstraintViolations specifically expects ConstraintViolation<Object>
        super.processConstraintViolations((Set) violations, errors);
    }
}

This custom class can be used to populate a newly created BindingResult , achieving the goal of creating a BindingResult from a Set<ConstraintViolation<?>> .这个自定义类可用于填充新创建的BindingResult ,实现创建目标BindingResultSet<ConstraintViolation<?>>

private BindingResult toBindException(ConstraintViolationException e, Object object, String objectName) {
    BindingResult bindingResult = new BeanPropertyBindingResult(object, objectName);
    new AddConstraintViolationsToErrors().addConstraintViolations(e.getConstraintViolations(), bindingResult);
    return bindingResult;
}

Expanding on Kristiaan's answer, for testing purposes it is not necessary to create a spring context to validate using Spring's bindingResult.扩展 Kristiaan 的答案,出于测试目的,没有必要创建 spring 上下文来使用 Spring 的 bindingResult 进行验证。 The following is an example:下面是一个例子:

public class ValidatorTest {

    javax.validation.Validator javaxValidator = Validation.buildDefaultValidatorFactory().getValidator();
    org.springframework.validation.Validator springValidator = new SpringValidatorAdapter(javaxValidator);

    @Test
    public void anExampleTest() {

    JSR303AnnotatedClassToTest   ctt  = new JSR303AnnotatedClassToTest( ..init vars..)

    ... test setup...

    WebDataBinder dataBinder = new WebDataBinder(ctt);
    dataBinder.setValidator(springValidator);
    dataBinder.validate();
    BindingResult bindingResult = dataBinder.getBindingResult(); 

    ... test analysis ...

    }
}

This approach doesn't require creating a binding result ahead of time, the dataBinder builds the right one for you.这种方法不需要提前创建绑定结果,dataBinder 会为您构建正确的结果。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM