JSR303 - Bean Validation 介紹及最佳實踐

關於 Bean Validation

    在任什麼時候候,當你要處理一個應用程序的業務邏輯,數據校驗是你必需要考慮和麪對的事情。應用程序必須經過某種手段來確保輸入進來的數據從語義上來說是正確的。在一般的狀況下,應用程序是分層的,不一樣的層由不一樣的開發人員來完成。不少時候一樣的數據驗證邏輯會出如今不一樣的層,這樣就會致使代碼冗餘和一些管理的問題,好比說語義的一致性等。爲了不這樣的狀況發生,最好是將驗證邏輯與相應的域模型進行綁定。java

    Bean Validation 爲 JavaBean 驗證定義了相應的元數據模型和 API。缺省的元數據是 Java Annotations,經過使用 XML 能夠對原有的元數據信息進行覆蓋和擴展。在應用程序中,經過使用 Bean Validation 或是你本身定義的 constraint,例如 @NotNull , @Max ,@ZipCode, 就能夠確保數據模型(JavaBean)的正確性。constraint 能夠附加到字段,getter 方法,類或者接口上面。對於一些特定的需求,用戶能夠很容易的開發定製化的 constraint。Bean Validation 是一個運行時的數據驗證框架,在驗證以後驗證的錯誤信息會被立刻返回。web

    Hibernate Validator 是 Bean Validation 的參考實現 . Hibernate Validator 提供了 JSR 303 規範中全部內置 constraint 的實現,除此以外還有一些附加的 constraint。session


Bean Validation 中的 constraint

表 1. Bean Validation 中內置的 constraint

表 2. Hibernate Validator 附加的 constraint

    一個 constraint 一般由 annotation 和相應的 constraint validator 組成,它們是一對多的關係。也就是說能夠有多個 constraint validator 對應一個 annotation。在運行時,Bean Validation 框架自己會根據被註釋元素的類型來選擇合適的 constraint validator 對數據進行驗證。架構

    有些時候,在用戶的應用中須要一些更復雜的 constraint。Bean Validation 提供擴展 constraint 的機制。能夠經過兩種方法去實現,一種是組合現有的 constraint 來生成一個更復雜的 constraint,另一種是開發一個全新的 constraint。app

建立一個包含驗證邏輯的簡單應用(基於 JSP)

    在本文中,經過建立一個虛構的訂單管理系統(基於 JSP 的 web 應用)來演示如何在 Java 開發過程當中應用 Bean Validation。該簡化的系統可讓用戶建立和檢索訂單。框架

系統設計和運用的技術

圖 1. 系統架構jsp

圖 1 是報表管理系統的結構圖,是典型的 MVC(Model-View-Controller)應用。Controller 負責接收和處理請求,Servlet 扮演 Controller 的角色去處理請求、業務邏輯並轉向合適的 JSP 頁面。在 Servlet 中對數據進行驗證。JSP 扮演 View 的角色以圖型化界面的方式呈現 Model 中的數據方便用戶交互。Model 就是此係統進行操做的數據模型,咱們對這部分加以簡化不對數據進行持久化。ui

數據模型

圖 2. 數據模型spa

圖 2 展現的是訂單管理系統的數據模型。.net

聲明瞭 contraint 的 JavaBean

清單 1. Order.java

public class Order { 
    // 必須不爲 null, 大小是 10 
    @NotNull 
    @Size(min = 10, max = 10) 
    private String orderId; 
    // 必須不爲空 
    @NotEmpty 
    private String customer; 
    // 必須是一個電子信箱地址 
    @Email 
    private String email; 
    // 必須不爲空 
    @NotEmpty 
    private String address; 
    // 必須不爲 null, 必須是下面四個字符串'created', 'paid', 'shipped', 'closed'其中之一 
    // @Status 是一個定製化的 contraint 
    @NotNull 
    @Status 
    private String status; 
    // 必須不爲 null 
    @NotNull 
    private Date createDate; 
    // 嵌套驗證 
    @Valid private Product product; 
    … getter 和 setter 
}

清單 2. Product.java

public class Product { 
    // 必須非空 
    @NotEmpty 
    private String productName; 
    // 必須在 8000 至 10000 的範圍內 
    // @Price 是一個定製化的 constraint 
    @Price private float price; 
    … Getter 和 setter 
}

清單 3. OrderQuery.java

// 'to'所表示的日期必須在'from'所表示的日期以後 
// @QueryConstraint 是一個定製化的 constraint 
@QueryConstraint public class OrderQuery { 
    private Date from; 
    private Date to; 
    … omitted … 
    Getter and setter 
}

定製化的 constraint

@Price是一個定製化的 constraint,由兩個內置的 constraint 組合而成。

清單 4. @Price 的 annotation 部分

// @Max 和 @Min 都是內置的 constraint 
@Max(10000) 
@Min(8000) 
@Constraint(validatedBy = {}) 
@Documented 
@Target( { ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.FIELD }) 
@Retention(RetentionPolicy.RUNTIME) 
public @interface Price { 
    String message() default "錯誤的價格"; 
    Class<?>[] groups() default {}; 
    Class<? extends Payload>[] payload() default {}; 
}

@Status是一個新開發的 constraint.

清單 5. @Status 的 annotation 部分

@Constraint(validatedBy = {StatusValidator.class}) 
@Documented 
@Target( { ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.FIELD }) 
@Retention(RetentionPolicy.RUNTIME) 
public @interface Status { 
    String message() default "不正確的狀態 , 應該是 'created', 'paid', shipped', closed'其中之一";
    Class<?>[] groups() default {}; 
    Class<? extends Payload>[] payload() default {}; 
}

清單 6. @Status 的 constraint validator 部分

public class StatusValidator implements ConstraintValidator<Status, String>{ 
    private final String[] ALL_STATUS = {"created", "paid", "shipped", "closed"}; 
    public void initialize(Status status) { } 
    public boolean isValid(String value, ConstraintValidatorContext context) { 
        if(Arrays.asList(ALL_STATUS).contains(value)) 
            return true; 
        return false; 
     } 
}

Bean Validation API 使用示例

建立訂單

用戶在建立一條訂單記錄時,須要填寫如下信息:訂單編號,客戶,電子信箱,地址,狀態,產品名稱,產品價格

圖 3. 建立訂單

對這些信息的校驗,使用 Bean Validation API

清單 7. 代碼片斷

protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 
    HttpSession session = req.getSession(); 
    // 從 request 中獲取輸入信息 
    String orderId = (String) req.getParameter("orderId"); 
    String customer = (String) req.getParameter("customer"); 
    String email = (String) req.getParameter("email"); 
    String address = (String) req.getParameter("address"); 
    String status = (String) req.getParameter("status"); 
    String productName = (String) req.getParameter("productName"); 
    String productPrice = (String) req.getParameter("productPrice"); 
    // 將 Bean 放入 session 中 Order order = new Order(); 
    order.setOrderId(orderId); 
    order.setCustomer(customer); 
    order.setEmail(email); 
    order.setAddress(address); 
    order.setStatus(status); 
    order.setCreateDate(new Date()); 
    Product product = new Product(); 
    product.setName(productName); 
    if(productPrice != null && productPrice.length() > 0) 
        product.setPrice(Float.valueOf(productPrice)); 
    order.setProduct(product); 
    session.setAttribute("order", order); 
    ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); 
    Validator validator = factory.getValidator(); 
    Set<ConstraintViolation<Order>> violations = validator.validate(order); 
    if(violations.size() == 0) { 
        session.setAttribute("order", null); 
        session.setAttribute("errorMsg", null); 
        resp.sendRedirect("creatSuccessful.jsp"); 
     } else { 
         StringBuffer buf = new StringBuffer(); 
         ResourceBundle bundle = ResourceBundle.getBundle("messages"); 
         for(ConstraintViolation<Order> violation: violations) { 
             buf.append("-" + bundle.getString(violation.getPropertyPath().toString())); 
             buf.append(violation.getMessage() + "<BR>\n"); 
          } 
          session.setAttribute("errorMsg", buf.toString()); 
          resp.sendRedirect("createOrder.jsp"); 
     } 
}

若是用戶不填寫任何信息提交訂單,相應的錯誤信息將會顯示在頁面上

圖 4. 驗證後返回錯誤信息

其實在整個程序的任何地方均可以調用 JSR 303 API 去對數據進行校驗,而後將校驗後的結果返回。

清單 8. 調用 JSR 303 API 進行校驗

Order order = new Order(); 
… ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); 
Validator validator = factory.getValidator(); 
Set<ConstraintViolation<Order>> violations = validator.validate(order);

結束語

JSR 303 的發佈使得在數據自動綁定和驗證變得簡單,使開發人員在定義數據模型時沒必要考慮實現框架的限制。固然 Bean Validation 還只是提供了一些最基本的 constraint,在實際的開發過程當中,用戶能夠根據本身的須要組合或開發出更加複雜的 constraint

相關文章
相關標籤/搜索