2012-02-21

Spring MVC 3.1.0 的兩種 Validation 方式

JSR-303 Validator 與 Spring Validator。

先來看我覺得不好用的 JSR-303 Validator。

在 Spring MVC 裡設定 JSR-303 Validator

第一、要有 JSR-303 實做(Provider),目前知道是 Hibernate Validator,Maven 定義如下:

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>4.2.0.Final</version>
</dependency>
會帶入 JSR-303 定義 validation-api-1.0.0.GA.jar 與實做 hibernate-validator-4.2.0.Final.jar。

第二、在 Spring 裡加入以下定義:
<mvc:annotation-driven />
第三、在 classpath 裡加入 ValidationMessages.properties 以提供錯誤訊息,如果沒有提供,則會使用 Hibernate Validator 預設的訊息檔,可以在 hibernate-validator-4.2.0.Final.jar/org/hibernate/validator 裡找到,建議拿來修改成自己的訊息檔,因為可以知道有哪些 key 值要提供。

再來只要在 @Controller 裡遇到 @Valid,就會進行 JSR-303 的驗證。

Controller
@RequestMapping(method = RequestMethod.POST)
public String sync(@Valid @ModelAttribute("cmd") Product cmd, BindingResult result) {
    if (result.hasErrors()) {
        return "productForm";
    }
    if (cmd.getId() == null) {
        this.productService.addProduct(cmd);
    }
    else {
        this.productService.updateProduct(cmd);
    }
    return "redirect:/product/list.do";
}
Model
@Entity
@SuppressWarnings("serial")
public class Product {

    @NotNull
    private String key;
    @Size(min = 10, max = 100)
    private String description;

    ...
}
再來看 Spring Validator。

在 Spring MVC 裡設定 Spring Validator

第一、在 @InitBinder 裡設定 Validator。

Controller
@InitBinder("cmd")
public void initBinder(WebDataBinder binder) {
    binder.setValidator(new ProductValidator());
}
ProductValidator
public class ProductValidator implements Validator {

    @Override
    public boolean supports(Class<?> clazz) {
        return Product.class.equals(clazz);
    }

    @Override
    public void validate(Object target, Errors errors) {
        Product cmd = (Product) target;
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "key", "key.empty");
        // ...
    }
}
第二、先在 Spring 裡設定 MessageSource,再於 classpath 裡加入 validation.properties 以提供錯誤訊息。
<bean id="messageSource"
    class="org.springframework.context.support.ResourceBundleMessageSource">
    <property name="basenames">
        <list>
            <value>validation</value>
        </list>
    </property>
</bean>
再來只要在 @Controller 裡遇到 @Valid,就會進行 Spring Validator 的驗證,特別注意,不是 binder.setValidator(...) 就會進行驗證,還必須加上 @Valid 才行。

JSR-303 與 Spring Validator 無法同時並存?

有趣的一點是,Spring Validator 與 JSR-303 都得加上 @Valid,除此之外的步驟並不一樣,所以當兩種驗證方式都設定時,會發生什麼事?兩個驗證都會執行嗎?經過演練發現,只要呼叫了 binder.setValidator(...) 就會切換成 Spring Validator 模式,不然就是 JSR-303 模式。

那可以兩種模式都用嗎?以下只是無聊的舉動。

在 @Controller 使用 JSR-303 模式,然後在 Post request 裡手動呼叫 Spring Validator 就可以了,這也是 Spring 強調 Spring Validator 不是專供 Spring MVC 使用,在一般的 Spring 程式裡就可以使用 Spring Validator。
@RequestMapping(method = RequestMethod.POST)
public String sync(@Valid @ModelAttribute("cmd") Product cmd, BindingResult result) {
    new ProductValidator().validate(cmd, result);
    if (result.hasErrors()) {
        return "productForm";
    }
    if (cmd.getId() == null) {
        this.productService.addProduct(cmd);
    }
    else {
        this.productService.updateProduct(cmd);
    }
    return "redirect:/product/list.do";
}
最後來解釋我為什麼覺得 JSR-303 難用:
第一、JSR-303 提供的驗證方式不多。


第二、可能是我沒有深入研究 JSR-303,我不知道怎麼作多欄位驗證或者需要資料庫查詢的唯一值驗證。

相關文章

沒有留言:

張貼留言