javascript
JSR 303 – Bean Validation 介绍及最佳实践
關(guān)于 Bean Validation
在任何時候,當(dāng)你要處理一個應(yīng)用程序的業(yè)務(wù)邏輯,數(shù)據(jù)校驗是你必須要考慮和面對的事情。應(yīng)用程序必須通過某種手段來確保輸入進(jìn)來的數(shù)據(jù)從語義上來講是正確的。在通常的情況下,應(yīng)用程序是分層的,不同的層由不同的開發(fā)人員來完成。很多時候同樣的數(shù)據(jù)驗證邏輯會出現(xiàn)在不同的層,這樣就會導(dǎo)致代碼冗余和一些管理的問題,比如說語義的一致性等。為了避免這樣的情況發(fā)生,最好是將驗證邏輯與相應(yīng)的域模型進(jìn)行綁定。
Bean Validation 為 JavaBean 驗證定義了相應(yīng)的元數(shù)據(jù)模型和 API。缺省的元數(shù)據(jù)是 Java Annotations,通過使用 XML 可以對原有的元數(shù)據(jù)信息進(jìn)行覆蓋和擴展。在應(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) | 被注釋的元素必須符合指定的正則表達(dá)式 |
表 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ù)進(jìn)行驗證。
有些時候,在用戶的應(yīng)用中需要一些更復(fù)雜的 constraint。Bean Validation 提供擴展 constraint 的機制。可以通過兩種方法去實現(xiàn),一種是組合現(xiàn)有的 constraint 來生成一個更復(fù)雜的 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 負(fù)責(zé)接收和處理請求,Servlet 扮演 Controller 的角色去處理請求、業(yè)務(wù)邏輯并轉(zhuǎn)向合適的 JSP 頁面。在 Servlet 中對數(shù)據(jù)進(jìn)行驗證。JSP 扮演 View 的角色以圖型化界面的方式呈現(xiàn) Model 中的數(shù)據(jù)方便用戶交互。Model 就是此系統(tǒng)進(jìn)行操作的數(shù)據(jù)模型,我們對這部分加以簡化不對數(shù)據(jù)進(jìn)行持久化。
數(shù)據(jù)模型
圖 2. 數(shù)據(jù)模型
?
圖 2 展示的是訂單管理系統(tǒng)的數(shù)據(jù)模型。
聲明了 contraint 的 JavaBean
清單 1. Order.java
public class Order {// 必須不為 null, 大小是 10@NotNull@Size(min = 10, max = 10)private String orderId;// 必須不為空@NotEmptyprivate String customer;// 必須是一個電子信箱地址@Emailprivate String email;// 必須不為空@NotEmptyprivate String address;// 必須不為 null, 必須是下面四個字符串'created', 'paid', 'shipped', 'closed'其中之一// @Status 是一個定制化的 contraint@NotNull@Statusprivate String status;// 必須不為 null@NotNullprivate Date createDate;// 嵌套驗證@Validprivate Product product;...getter 和 setter}顯示更多
清單 2. Product.java
public class Product {// 必須非空@NotEmptyprivate String productName;// 必須在 8000 至 10000 的范圍內(nèi)// @Price 是一個定制化的 constraint@Priceprivate float price; ...Getter 和 setter}顯示更多
清單 3. OrderQuery.java
// 'to'所表示的日期必須在'from'所表示的日期之后// @QueryConstraint 是一個定制化的 constraint@QueryConstraintpublic class OrderQuery {private Date from;private Date to; ... omitted...Getter and setter}顯示更多
定制化的 constraint
@Price?是一個定制化的 constraint,由兩個內(nèi)置的 constraint 組合而成。
清單 4. @Price 的 annotation 部分
// @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 部分
@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 部分
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. 代碼片段
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ù)進(jìn)行校驗,然后將校驗后的結(jié)果返回。
清單 8. 調(diào)用 JSR 303 API 進(jìn)行校驗
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)框架的限制。當(dāng)然 Bean Validation 還只是提供了一些最基本的 constraint,在實際的開發(fā)過程中,用戶可以根據(jù)自己的需要組合或開發(fā)出更加復(fù)雜的 constraint
總結(jié)
以上是生活随笔為你收集整理的JSR 303 – Bean Validation 介绍及最佳实践的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Git命令按人统计提交次数和代码量
- 下一篇: Mock工具之Mockito实战