JSR 303 - Bean Validation 介绍及最佳实践
關(guān)于 Bean Validation
在任何時候,當你要處理一個應(yīng)用程序的業(yè)務(wù)邏輯,數(shù)據(jù)校驗是你必須要考慮和面對的事情。應(yīng)用程序必須通過某種手段來確保輸入進來的數(shù)據(jù)從語義上來講是正確的。在通常的情況下,應(yīng)用程序是分層的,不同的層由不同的開發(fā)人員來完成。很多時候同樣的數(shù)據(jù)驗證邏輯會出現(xiàn)在不同的層,這樣就會導致代碼冗余和一些管理的問題,比如說語義的一致性等。為了避免這樣的情況發(fā)生,最好是將驗證邏輯與相應(yīng)的域模型進行綁定。
Bean Validation 為 JavaBean 驗證定義了相應(yīng)的元數(shù)據(jù)模型和 API。缺省的元數(shù)據(jù)是 Java Annotations,通過使用 XML 可以對原有的元數(shù)據(jù)信息進行覆蓋和擴展。在應(yīng)用程序中,通過使用 Bean Validation 或是你自己定義的 constraint,例如?@NotNull,?@Max,?@ZipCode, 就可以確保數(shù)據(jù)模型(JavaBean)的正確性。constraint 可以附加到字段,getter 方法,類或者接口上面。對于一些特定的需求,用戶可以很容易的開發(fā)定制化的 constraint。Bean Validation 是一個運行時的數(shù)據(jù)驗證框架,在驗證之后驗證的錯誤信息會被馬上返回。
下載 JSR 303 – Bean Validation 規(guī)范?http://jcp.org/en/jsr/detail?id=303
Hibernate Validator 是 Bean Validation 的參考實現(xiàn) . Hibernate Validator 提供了 JSR 303 規(guī)范中所有內(nèi)置 constraint 的實現(xiàn),除此之外還有一些附加的 constraint。如果想了解更多有關(guān) Hibernate Validator 的信息,請查看?http://www.hibernate.org/subprojects/validator.html
Bean Validation 中的 constraint
表 1. Bean Validation 中內(nèi)置的 constraint
| @Null | 被注釋的元素必須為?null |
| @NotNull | 被注釋的元素必須不為?null |
| @AssertTrue | 被注釋的元素必須為?true |
| @AssertFalse | 被注釋的元素必須為?false |
| @Min(value) | 被注釋的元素必須是一個數(shù)字,其值必須大于等于指定的最小值 |
| @Max(value) | 被注釋的元素必須是一個數(shù)字,其值必須小于等于指定的最大值 |
| @DecimalMin(value) | 被注釋的元素必須是一個數(shù)字,其值必須大于等于指定的最小值 |
| @DecimalMax(value) | 被注釋的元素必須是一個數(shù)字,其值必須小于等于指定的最大值 |
| @Size(max, min) | 被注釋的元素的大小必須在指定的范圍內(nèi) |
| @Digits (integer, fraction) | 被注釋的元素必須是一個數(shù)字,其值必須在可接受的范圍內(nèi) |
| @Past | 被注釋的元素必須是一個過去的日期 |
| @Future | 被注釋的元素必須是一個將來的日期 |
| @Pattern(value) | 被注釋的元素必須符合指定的正則表達式 |
表 2. Hibernate Validator 附加的 constraint
| 被注釋的元素必須是電子郵箱地址 | |
| @Length | 被注釋的字符串的大小必須在指定的范圍內(nèi) |
| @NotEmpty | 被注釋的字符串的必須非空 |
| @Range | 被注釋的元素必須在合適的范圍內(nèi) |
一個 constraint 通常由 annotation 和相應(yīng)的 constraint validator 組成,它們是一對多的關(guān)系。也就是說可以有多個 constraint validator 對應(yīng)一個 annotation。在運行時,Bean Validation 框架本身會根據(jù)被注釋元素的類型來選擇合適的 constraint validator 對數(shù)據(jù)進行驗證。
有些時候,在用戶的應(yīng)用中需要一些更復雜的 constraint。Bean Validation 提供擴展 constraint 的機制。可以通過兩種方法去實現(xiàn),一種是組合現(xiàn)有的 constraint 來生成一個更復雜的 constraint,另外一種是開發(fā)一個全新的 constraint。
創(chuàng)建一個包含驗證邏輯的簡單應(yīng)用(基于 JSP)
在本文中,通過創(chuàng)建一個虛構(gòu)的訂單管理系統(tǒng)(基于 JSP 的 web 應(yīng)用)來演示如何在 Java 開發(fā)過程中應(yīng)用 Bean Validation。該簡化的系統(tǒng)可以讓用戶創(chuàng)建和檢索訂單。
系統(tǒng)設(shè)計和運用的技術(shù)
圖 1. 系統(tǒng)架構(gòu)
圖 1 是報表管理系統(tǒng)的結(jié)構(gòu)圖,是典型的 MVC(Model-View-Controller)應(yīng)用。Controller 負責接收和處理請求,Servlet 扮演 Controller 的角色去處理請求、業(yè)務(wù)邏輯并轉(zhuǎn)向合適的 JSP 頁面。在 Servlet 中對數(shù)據(jù)進行驗證。JSP 扮演 View 的角色以圖型化界面的方式呈現(xiàn) Model 中的數(shù)據(jù)方便用戶交互。Model 就是此系統(tǒng)進行操作的數(shù)據(jù)模型,我們對這部分加以簡化不對數(shù)據(jù)進行持久化。
數(shù)據(jù)模型
圖 2. 數(shù)據(jù)模型
圖 2 展示的是訂單管理系統(tǒng)的數(shù)據(jù)模型。
聲明了 contraint 的 JavaBean
清單 1. Order.java
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | public class Order { ?// 必須不為 null, 大小是 10 ?@NotNull ?@Size(min = 10, max = 10) ?private String orderId; ?// 必須不為空 ?@NotEmpty ?private String customer; ?// 必須是一個電子信箱地址 ?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
| 1 2 3 4 5 6 7 8 9 10 11 | public class Product { ?// 必須非空 ?@NotEmpty ?private String productName; ?// 必須在 8000 至 10000 的范圍內(nèi) ?// @Price 是一個定制化的 constraint ?@Price ?private float price; … ?Getter 和 setter ?} |
清單 3. OrderQuery.java
| 1 2 3 4 5 6 7 8 9 | // 'to'所表示的日期必須在'from'所表示的日期之后 ?// @QueryConstraint 是一個定制化的 constraint ?@QueryConstraint ?public class OrderQuery { ?private Date from; ?private Date to; … omitted … ?Getter and setter ?} |
定制化的 constraint
@Price是一個定制化的 constraint,由兩個內(nèi)置的 constraint 組合而成。
清單 4. @Price 的 annotation 部分
| 1 2 3 4 5 6 7 8 9 10 11 12 | // @Max 和 @Min 都是內(nèi)置的 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是一個新開發(fā)的 constraint.
清單 5. @Status 的 annotation 部分
| 1 2 3 4 5 6 7 8 9 | @Constraint(validatedBy = {StatusValidator.class}) @Documented @Target( { ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.FIELD }) @Retention(RetentionPolicy.RUNTIME) public @interface Status { String message() default "不正確的狀態(tài) , 應(yīng)該是 'created', 'paid', shipped', closed'其中之一"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; } |
清單 6. @Status 的 constraint validator 部分
| 1 2 3 4 5 6 7 8 9 10 | 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 使用示例
創(chuàng)建訂單
用戶在創(chuàng)建一條訂單記錄時,需要填寫以下信息:訂單編號,客戶,電子信箱,地址,狀態(tài),產(chǎn)品名稱,產(chǎn)品價格
圖 3. 創(chuàng)建訂單
對這些信息的校驗,使用 Bean Validation API
清單 7. 代碼片段
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | 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"); } } |
如果用戶不填寫任何信息提交訂單,相應(yīng)的錯誤信息將會顯示在頁面上
圖 4. 驗證后返回錯誤信息
其實在整個程序的任何地方都可以調(diào)用 JSR 303 API 去對數(shù)據(jù)進行校驗,然后將校驗后的結(jié)果返回。
清單 8. 調(diào)用 JSR 303 API 進行校驗
| 1 2 3 4 5 | Order order = new Order(); … ?ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); ?Validator validator = factory.getValidator(); ?Set<ConstraintViolation<Order>> violations = validator.validate(order); |
…
結(jié)束語
JSR 303 的發(fā)布使得在數(shù)據(jù)自動綁定和驗證變得簡單,使開發(fā)人員在定義數(shù)據(jù)模型時不必考慮實現(xiàn)框架的限制。當然 Bean Validation 還只是提供了一些最基本的 constraint,在實際的開發(fā)過程中,用戶可以根據(jù)自己的需要組合或開發(fā)出更加復雜的 constraint
相關(guān)主題
- JSR 303 規(guī)范:詳細的 JSR 303 規(guī)范描述,在這里可以找到和規(guī)范相關(guān)的詳細文檔,并可以下載相關(guān)實現(xiàn)代碼。
- JSR 303 參考實現(xiàn) Hibernate Validator:了解如何具體通過 Hibernate 來實現(xiàn) Bean Validator。
- developerWorks Java 技術(shù)專區(qū):這里有數(shù)百篇關(guān)于 Java 編程各個方面的文章。
總結(jié)
以上是生活随笔為你收集整理的JSR 303 - Bean Validation 介绍及最佳实践的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MySQL优化器:index merge
- 下一篇: JSR 303 – Bean Valid