javascript
JSR 303 - Bean Validation 介绍及最佳实践
關于 Bean Validation
在任何時候,當你要處理一個應用程序的業務邏輯,數據校驗是你必須要考慮和面對的事情。應用程序必須通過某種手段來確保輸入進來的數據從語義上來講是正確的。在通常的情況下,應用程序是分層的,不同的層由不同的開發人員來完成。很多時候同樣的數據驗證邏輯會出現在不同的層,這樣就會導致代碼冗余和一些管理的問題,比如說語義的一致性等。為了避免這樣的情況發生,最好是將驗證邏輯與相應的域模型進行綁定。
Bean Validation 為 JavaBean 驗證定義了相應的元數據模型和 API。缺省的元數據是 Java Annotations,通過使用 XML 可以對原有的元數據信息進行覆蓋和擴展。在應用程序中,通過使用 Bean Validation 或是你自己定義的 constraint,例如?@NotNull,?@Max,?@ZipCode, 就可以確保數據模型(JavaBean)的正確性。constraint 可以附加到字段,getter 方法,類或者接口上面。對于一些特定的需求,用戶可以很容易的開發定制化的 constraint。Bean Validation 是一個運行時的數據驗證框架,在驗證之后驗證的錯誤信息會被馬上返回。
下載 JSR 303 – Bean Validation 規范?http://jcp.org/en/jsr/detail?id=303
Hibernate Validator 是 Bean Validation 的參考實現 . Hibernate Validator 提供了 JSR 303 規范中所有內置 constraint 的實現,除此之外還有一些附加的 constraint。如果想了解更多有關 Hibernate Validator 的信息,請查看?http://www.hibernate.org/subprojects/validator.html
Bean Validation 中的 constraint
表 1. Bean Validation 中內置的 constraint
| @Null | 被注釋的元素必須為?null |
| @NotNull | 被注釋的元素必須不為?null |
| @AssertTrue | 被注釋的元素必須為?true |
| @AssertFalse | 被注釋的元素必須為?false |
| @Min(value) | 被注釋的元素必須是一個數字,其值必須大于等于指定的最小值 |
| @Max(value) | 被注釋的元素必須是一個數字,其值必須小于等于指定的最大值 |
| @DecimalMin(value) | 被注釋的元素必須是一個數字,其值必須大于等于指定的最小值 |
| @DecimalMax(value) | 被注釋的元素必須是一個數字,其值必須小于等于指定的最大值 |
| @Size(max, min) | 被注釋的元素的大小必須在指定的范圍內 |
| @Digits (integer, fraction) | 被注釋的元素必須是一個數字,其值必須在可接受的范圍內 |
| @Past | 被注釋的元素必須是一個過去的日期 |
| @Future | 被注釋的元素必須是一個將來的日期 |
| @Pattern(value) | 被注釋的元素必須符合指定的正則表達式 |
表 2. Hibernate Validator 附加的 constraint
| 被注釋的元素必須是電子郵箱地址 | |
| @Length | 被注釋的字符串的大小必須在指定的范圍內 |
| @NotEmpty | 被注釋的字符串的必須非空 |
| @Range | 被注釋的元素必須在合適的范圍內 |
一個 constraint 通常由 annotation 和相應的 constraint validator 組成,它們是一對多的關系。也就是說可以有多個 constraint validator 對應一個 annotation。在運行時,Bean Validation 框架本身會根據被注釋元素的類型來選擇合適的 constraint validator 對數據進行驗證。
有些時候,在用戶的應用中需要一些更復雜的 constraint。Bean Validation 提供擴展 constraint 的機制。可以通過兩種方法去實現,一種是組合現有的 constraint 來生成一個更復雜的 constraint,另外一種是開發一個全新的 constraint。
創建一個包含驗證邏輯的簡單應用(基于 JSP)
在本文中,通過創建一個虛構的訂單管理系統(基于 JSP 的 web 應用)來演示如何在 Java 開發過程中應用 Bean Validation。該簡化的系統可以讓用戶創建和檢索訂單。
系統設計和運用的技術
圖 1. 系統架構
圖 1 是報表管理系統的結構圖,是典型的 MVC(Model-View-Controller)應用。Controller 負責接收和處理請求,Servlet 扮演 Controller 的角色去處理請求、業務邏輯并轉向合適的 JSP 頁面。在 Servlet 中對數據進行驗證。JSP 扮演 View 的角色以圖型化界面的方式呈現 Model 中的數據方便用戶交互。Model 就是此系統進行操作的數據模型,我們對這部分加以簡化不對數據進行持久化。
數據模型
圖 2. 數據模型
圖 2 展示的是訂單管理系統的數據模型。
聲明了 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 的范圍內 ?// @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,由兩個內置的 constraint 組合而成。
清單 4. @Price 的 annotation 部分
| 1 2 3 4 5 6 7 8 9 10 11 12 | // @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 部分
| 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 "不正確的狀態 , 應該是 '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 使用示例
創建訂單
用戶在創建一條訂單記錄時,需要填寫以下信息:訂單編號,客戶,電子信箱,地址,狀態,產品名稱,產品價格
圖 3. 創建訂單
對這些信息的校驗,使用 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"); } } |
如果用戶不填寫任何信息提交訂單,相應的錯誤信息將會顯示在頁面上
圖 4. 驗證后返回錯誤信息
其實在整個程序的任何地方都可以調用 JSR 303 API 去對數據進行校驗,然后將校驗后的結果返回。
清單 8. 調用 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); |
…
結束語
JSR 303 的發布使得在數據自動綁定和驗證變得簡單,使開發人員在定義數據模型時不必考慮實現框架的限制。當然 Bean Validation 還只是提供了一些最基本的 constraint,在實際的開發過程中,用戶可以根據自己的需要組合或開發出更加復雜的 constraint
相關主題
- JSR 303 規范:詳細的 JSR 303 規范描述,在這里可以找到和規范相關的詳細文檔,并可以下載相關實現代碼。
- JSR 303 參考實現 Hibernate Validator:了解如何具體通過 Hibernate 來實現 Bean Validator。
- developerWorks Java 技術專區:這里有數百篇關于 Java 編程各個方面的文章。
總結
以上是生活随笔為你收集整理的JSR 303 - Bean Validation 介绍及最佳实践的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MySQL优化器:index merge
- 下一篇: Resilience4j-轻量级熔断框架