簡體   English   中英

將 JSR-303 驗證錯誤轉換為 Spring 的 BindingResult

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

我在 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);
    ...
}

是否可以將errors映射到 Spring 的BindingResult對象,而無需手動檢查所有錯誤並將它們添加到BindingResult 像這樣的東西:

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

我知道可以使用@Valid注釋CustomForm參數並讓 Spring 注入BindingResult作為另一個方法的參數,但在我的情況下這不是一個選項。

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

一種更簡單的方法可能是使用 Spring 的抽象org.springframework.validation.Validator ,您可以通過在上下文中使用此 bean 來獲取驗證器:

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

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

有了這個抽象,你就可以這樣使用驗證器,傳入你的 bindingResult :

validator.validate(obj, bindingResult);

Spring 使用 SpringValidatorAdapter 將 javax.validation.ConstraintViolation 對象轉換為 ObjectError 或 FieldError 對象,如綁定結果中所示。 然后 BindStatus 使用消息源(如 Web 應用程序上下文本身)來轉換錯誤。 簡而言之,你可以這樣做:

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

這在編寫單元測試時更容易,因為您甚至不需要創建 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);
    }
}

與此問題的其他答案不同,此解決方案處理已經存在需要轉換為BindingResultSet<ConstraintViolation<?>>BindingResult

解釋

Spring 提供SpringValidatorAdapter類來執行 bean 驗證,將結果存儲在Errors實例中(注意BindingResult擴展Errors )。 此類的正常手動使用是使用它通過validate方法執行驗證:

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

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

但是,這在已經存在需要轉換為BindingResultSet<ConstraintViolation<?>>的情況下BindingResult

仍然有可能實現這個目標,盡管它確實需要跳過幾個額外的箍。 SpringValidatorAdapter包含一個processConstraintViolations方法,該方法將ConstraintViolation對象轉換為適當的 Spring ObjectError子類型,並將它們存儲在Errors對象上。 然而,這個方法是受保護的,限制了它對子類的訪問。

可以通過創建SpringValidatorAdapter的自定義子類來解決此限制,該子類委托或公開受保護的方法。 這不是典型的用法,但它有效。

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

這個自定義類可用於填充新創建的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;
}

擴展 Kristiaan 的答案,出於測試目的,沒有必要創建 spring 上下文來使用 Spring 的 bindingResult 進行驗證。 下面是一個例子:

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 ...

    }
}

這種方法不需要提前創建綁定結果,dataBinder 會為您構建正確的結果。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM